(適配器模式呜叫、裝飾模式帐要、代理模式侦香、外觀模式落塑、橋接模式、組合模式罐韩、享元模式)
原文作者:終點(diǎn) 出處:Java之美[從菜鳥到高手演變]之設(shè)計(jì)模式
本章涉及7種結(jié)構(gòu)型模式:適配器模式憾赁、裝飾模式、代理模式散吵、外觀模式龙考、橋接模式、組合模式矾睦、享元模式晦款。其中對象的適配器模式是各種模式的起源,我們看下面的圖:
6顷锰、適配器模式(Adapter)
適配器模式將某個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口表示柬赐,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為三類:類的適配器模式官紫、對象的適配器模式肛宋、接口的適配器模式州藕。首先,我們來看看類的適配器模式酝陈,先看類圖:
核心思想就是:有一個(gè)Source類床玻,擁有一個(gè)方法,待適配沉帮,目標(biāo)接口時(shí)Targetable锈死,通過Adapter類,將Source的功能擴(kuò)展到Targetable里穆壕,看代碼:
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
public interface Targetable {
/* 與原類中的方法相同 */
public void method1();
/* 新類的方法 */
public void method2();
}
public class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
}
Adapter類繼承Source類待牵,實(shí)現(xiàn)Targetable接口,下面是測試類:
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
輸出:
this is original method!
this is the targetable method!
這樣Targetable接口的實(shí)現(xiàn)類就具有了Source類的功能喇勋。
對象的適配器模式
基本思路和類的適配器模式相同缨该,只是將Adapter類作修改,這次不繼承Source類川背,而是持有Source類的實(shí)例贰拿,以達(dá)到解決兼容性的問題∠ㄔ疲看圖:
只需要修改Adapter類的源碼即可:
public class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source){
super();
this.source = source;
}
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
@Override
public void method1() {
source.method1();
}
}
測試類:
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
輸出與第一種一樣膨更,只是適配的方法不同而已。
第三種適配器模式是接口的適配器模式缴允,接口的適配器是這樣的:有時(shí)我們寫的一個(gè)接口中有多個(gè)抽象方法荚守,當(dāng)我們寫該接口的實(shí)現(xiàn)類時(shí),必須實(shí)現(xiàn)該接口的所有方法练般,這明顯有時(shí)比較浪費(fèi)健蕊,因?yàn)椴⒉皇撬械姆椒ǘ际俏覀冃枰模袝r(shí)只需要某一些踢俄,此處為了解決這個(gè)問題缩功,我們引入了接口的適配器模式,借助于一個(gè)抽象類都办,該抽象類實(shí)現(xiàn)了該接口嫡锌,實(shí)現(xiàn)了所有的方法,而我們不和原始的接口打交道琳钉,只和該抽象類取得聯(lián)系势木,所以我們寫一個(gè)類,繼承該抽象類歌懒,重寫我們需要的方法就行啦桌。看一下類圖:
這個(gè)很好理解,在實(shí)際開發(fā)中甫男,我們也常會遇到這種接口中定義了太多的方法且改,以致于有時(shí)我們在一些實(shí)現(xiàn)類中并不是都需要“宀担看代碼:
public interface Sourceable {
public void method1();
public void method2();
}
抽象類Wrapper2:
public abstract class Wrapper2 implements Sourceable{
public void method1(){}
public void method2(){}
}
public class SourceSub1 extends Wrapper2 {
public void method1(){
System.out.println("the sourceable interface's first Sub1!");
}
}
public class SourceSub2 extends Wrapper2 {
public void method2(){
System.out.println("the sourceable interface's second Sub2!");
}
}
public class WrapperTest {
public static void main(String[] args) {
Sourceable source1 = new SourceSub1();
Sourceable source2 = new SourceSub2();
source1.method1();
source1.method2();
source2.method1();
source2.method2();
}
}
測試輸出:
the sourceable interface's first Sub1!
the sourceable interface's second Sub2!
達(dá)到了我們的效果又跛!
講了這么多,總結(jié)一下三種適配器模式的應(yīng)用場景:
類的適配器模式:當(dāng)希望將一個(gè)類轉(zhuǎn)換成滿足另一個(gè)新接口的類時(shí)若治,可以使用類的適配器模式慨蓝,創(chuàng)建一個(gè)新類,繼承原有的類端幼,實(shí)現(xiàn)新的接口即可礼烈。
對象的適配器模式:當(dāng)希望將一個(gè)對象轉(zhuǎn)換成滿足另一個(gè)新接口的對象時(shí),可以創(chuàng)建一個(gè)Wrapper類婆跑,持有原類的一個(gè)實(shí)例济丘,在Wrapper類的方法中,調(diào)用實(shí)例的方法就行洽蛀。
接口的適配器模式:當(dāng)不希望實(shí)現(xiàn)一個(gè)接口中所有的方法時(shí),可以創(chuàng)建一個(gè)抽象類Wrapper疟赊,實(shí)現(xiàn)所有方法郊供,我們寫別的類的時(shí)候,繼承抽象類即可近哟。
7驮审、裝飾模式(Decorator)
顧名思義,裝飾模式就是給一個(gè)對象增加一些新的功能吉执,而且是動態(tài)的疯淫,要求裝飾對象和被裝飾對象實(shí)現(xiàn)同一個(gè)接口,裝飾對象持有被裝飾對象的實(shí)例戳玫,關(guān)系圖如下:
Source類是被裝飾類熙掺,Decorator類是一個(gè)裝飾類,可以為Source類動態(tài)的添加一些功能咕宿,代碼如下:
public interface Sourceable {
public void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source){
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!");
source.method();
System.out.println("after decorator!");
}
}
測試類:
public class DecoratorTest {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
輸出:
before decorator!
the original method!
after decorator!
裝飾器模式的應(yīng)用場景:
1币绩、需要擴(kuò)展一個(gè)類的功能。
2府阀、動態(tài)的為一個(gè)對象增加功能缆镣,而且還能動態(tài)撤銷。(繼承不能做到這一點(diǎn)试浙,繼承的功能是靜態(tài)的董瞻,不能動態(tài)增刪。)
缺點(diǎn):產(chǎn)生過多相似的對象田巴,不易排錯(cuò)钠糊!
8挟秤、代理模式(Proxy)
其實(shí)每個(gè)模式名稱就表明了該模式的作用,代理模式就是多一個(gè)代理類出來眠蚂,替原對象進(jìn)行一些操作煞聪,比如我們在租房子的時(shí)候回去找中介,為什么呢逝慧?因?yàn)槟銓υ摰貐^(qū)房屋的信息掌握的不夠全面昔脯,希望找一個(gè)更熟悉的人去幫你做,此處的代理就是這個(gè)意思笛臣。再如我們有的時(shí)候打官司云稚,我們需要請律師,因?yàn)槁蓭熢诜煞矫嬗袑iL沈堡,可以替我們進(jìn)行操作静陈,表達(dá)我們的想法。先來看看關(guān)系圖:
根據(jù)上文的闡述诞丽,代理模式就比較容易的理解了鲸拥,我們看下代碼:
public interface Sourceable {
public void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
測試類:
public class ProxyTest {
public static void main(String[] args) {
Sourceable source = new Proxy();
source.method();
}
}
輸出:
before proxy!
the original method!
after proxy!
代理模式的應(yīng)用場景:
如果已有的方法在使用的時(shí)候需要對原有的方法進(jìn)行改進(jìn),此時(shí)有兩種辦法:
1僧免、修改原有的方法來適應(yīng)刑赶。這樣違反了“對擴(kuò)展開放,對修改關(guān)閉”的原則懂衩。
2撞叨、就是采用一個(gè)代理類調(diào)用原有的方法,且對產(chǎn)生的結(jié)果進(jìn)行控制浊洞。這種方法就是代理模式牵敷。
使用代理模式揩徊,可以將功能劃分的更加清晰窟哺,有助于后期維護(hù)!
9寝优、外觀模式(Facade)
外觀模式是為了解決類與類之家的依賴關(guān)系的苫亦,像spring一樣尖淘,可以將類和類之間的關(guān)系配置到配置文件中,而外觀模式就是將他們的關(guān)系放在一個(gè)Facade類中著觉,降低了類類之間的耦合度村生,該模式中沒有涉及到接口,看下類圖:(我們以一個(gè)計(jì)算機(jī)的啟動過程為例)
我們先看下實(shí)現(xiàn)類:
public class CPU {
public void startup(){
System.out.println("cpu startup!");
}
public void shutdown(){
System.out.println("cpu shutdown!");
}
}
public class Memory {
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
public class Disk {
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
User類如下:
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
輸出:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
如果我們沒有Computer類饼丘,那么趁桃,CPU、Memory、Disk他們之間將會相互持有實(shí)例卫病,產(chǎn)生關(guān)系油啤,這樣會造成嚴(yán)重的依賴,修改一個(gè)類蟀苛,可能會帶來其他類的修改益咬,這不是我們想要看到的,有了Computer類帜平,他們之間的關(guān)系被放在了Computer類里幽告,這樣就起到了解耦的作用,這裆甩,就是外觀模式冗锁!
10、橋接模式(Bridge)
橋接模式就是把事物和其具體實(shí)現(xiàn)分開嗤栓,使他們可以各自獨(dú)立的變化冻河。橋接的用意是:將抽象化與實(shí)現(xiàn)化解耦,使得二者可以獨(dú)立變化茉帅,像我們常用的JDBC橋DriverManager一樣叨叙,JDBC進(jìn)行連接數(shù)據(jù)庫的時(shí)候,在各個(gè)數(shù)據(jù)庫之間進(jìn)行切換堪澎,基本不需要?jiǎng)犹嗟拇a擂错,甚至絲毫不用動,原因就是JDBC提供統(tǒng)一接口全封,每個(gè)數(shù)據(jù)庫提供各自的實(shí)現(xiàn),用一個(gè)叫做數(shù)據(jù)庫驅(qū)動的程序來橋接就行了桃犬。我們來看看關(guān)系圖:
實(shí)現(xiàn)代碼:
先定義接口:
public interface Sourceable {
public void method();
}
分別定義兩個(gè)實(shí)現(xiàn)類:
public class SourceSub1 implements Sourceable {
@Override
public void method() {
System.out.println("this is the first sub!");
}
}
public class SourceSub2 implements Sourceable {
@Override
public void method() {
System.out.println("this is the second sub!");
}
}
定義一個(gè)橋刹悴,持有Sourceable的一個(gè)實(shí)例:
public abstract class Bridge {
private Sourceable source;
public void method(){
source.method();
}
public Sourceable getSource() {
return source;
}
public void setSource(Sourceable source) {
this.source = source;
}
}
public class MyBridge extends Bridge {
public void method(){
getSource().method();
}
}
測試類:
public class BridgeTest {
public static void main(String[] args) {
Bridge bridge = new MyBridge();
/*調(diào)用第一個(gè)對象*/
Sourceable source1 = new SourceSub1();
bridge.setSource(source1);
bridge.method();
/*調(diào)用第二個(gè)對象*/
Sourceable source2 = new SourceSub2();
bridge.setSource(source2);
bridge.method();
}
}
output:
this is the first sub!
this is the second sub!
這樣,就通過對Bridge類的調(diào)用攒暇,實(shí)現(xiàn)了對接口Sourceable的實(shí)現(xiàn)類SourceSub1和SourceSub2的調(diào)用土匀。接下來我再畫個(gè)圖,大家就應(yīng)該明白了形用,因?yàn)檫@個(gè)圖是我們JDBC連接的原理就轧,有數(shù)據(jù)庫學(xué)習(xí)基礎(chǔ)的,一結(jié)合就都懂了田度。
11妒御、組合模式(Composite)
組合模式有時(shí)又叫部分-整體模式在處理類似樹形結(jié)構(gòu)的問題時(shí)比較方便,看看關(guān)系圖:
直接來看代碼:
public class TreeNode {
private String name;
private TreeNode parent;
private Vector<TreeNode> children = new Vector<TreeNode>();
public TreeNode(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
//添加孩子節(jié)點(diǎn)
public void add(TreeNode node){
children.add(node);
}
//刪除孩子節(jié)點(diǎn)
public void remove(TreeNode node){
children.remove(node);
}
//取得孩子節(jié)點(diǎn)
public Enumeration<TreeNode> getChildren(){
return children.elements();
}
}
public class Tree {
TreeNode root = null;
public Tree(String name) {
root = new TreeNode(name);
}
public static void main(String[] args) {
Tree tree = new Tree("A");
TreeNode nodeB = new TreeNode("B");
TreeNode nodeC = new TreeNode("C");
nodeB.add(nodeC);
tree.root.add(nodeB);
System.out.println("build the tree finished!");
}
}
使用場景:將多個(gè)對象組合在一起進(jìn)行操作镇饺,常用于表示樹形結(jié)構(gòu)中乎莉,例如二叉樹等。
12、享元模式(Flyweight)
享元模式的主要目的是實(shí)現(xiàn)對象的共享惋啃,即共享池哼鬓,當(dāng)系統(tǒng)中對象多的時(shí)候可以減少內(nèi)存的開銷,通常與工廠模式一起使用边灭。
FlyWeightFactory負(fù)責(zé)創(chuàng)建和管理享元單元异希,當(dāng)一個(gè)客戶端請求時(shí),工廠需要檢查當(dāng)前對象池中是否有符合條件的對象绒瘦,如果有称簿,就返回已經(jīng)存在的對象,如果沒有椭坚,則創(chuàng)建一個(gè)新對象予跌,F(xiàn)lyWeight是超類。一提到共享池善茎,我們很容易聯(lián)想到Java里面的JDBC連接池券册,想想每個(gè)連接的特點(diǎn),我們不難總結(jié)出:適用于作共享的一些個(gè)對象垂涯,他們有一些共有的屬性烁焙,就拿數(shù)據(jù)庫連接池來說,url耕赘、driverClassName骄蝇、username、password及dbname操骡,這些屬性對于每個(gè)連接來說都是一樣的九火,所以就適合用享元模式來處理,建一個(gè)工廠類册招,將上述類似屬性作為內(nèi)部數(shù)據(jù)岔激,其它的作為外部數(shù)據(jù),在方法調(diào)用時(shí)是掰,當(dāng)做參數(shù)傳進(jìn)來虑鼎,這樣就節(jié)省了空間,減少了實(shí)例的數(shù)量键痛。
看個(gè)例子:
看下數(shù)據(jù)庫連接池的代碼:
public class ConnectionPool {
private Vector<Connection> pool;
/*公有屬性*/
private String url = "jdbc:mysql://localhost:3306/test";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.jdbc.Driver";
private int poolSize = 100;
private static ConnectionPool instance = null;
Connection conn = null;
/*構(gòu)造方法炫彩,做一些初始化工作*/
private ConnectionPool() {
pool = new Vector<Connection>(poolSize);
for (int i = 0; i < poolSize; i++) {
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url, username, password);
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/* 返回連接到連接池 */
public synchronized void release() {
pool.add(conn);
}
/* 返回連接池中的一個(gè)數(shù)據(jù)庫連接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}
}
通過連接池的管理,實(shí)現(xiàn)了數(shù)據(jù)庫連接的共享絮短,不需要每一次都重新創(chuàng)建連接江兢,節(jié)省了數(shù)據(jù)庫重新創(chuàng)建的開銷,提升了系統(tǒng)的性能丁频!