博主在大三的時候有上過設計模式這一門課,但是當時很多都基本沒有聽懂,重點是也沒有細聽,因爲覺得沒什麼卵用,硬是要搞那麼複雜幹嘛。因此設計模式建議工作半年以上的猿友閱讀起來纔會理解的比較深刻。當然,你沒事做看看也是沒有壞處的。
總體來說設計模式分爲三大類:創建型模式、結構型模式和行爲型模式。
博主的上一篇文章已經提到過創建型模式,此外該文章還有設計模式概況和設計模式的六大原則。設計模式的六大原則是設計模式的核心思想,詳情請看博主的另外一篇文章: Java經典設計模式之五大創建模式(附實例和詳解)。
接下來我們看看結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。其中適配器模式主要分爲三類:類的適配器模式、對象的適配器模式、接口的適配器模式。其中的對象的適配器模式是各種結構型模式的起源。
一、適配器模式
適配器模式主要分爲三類:類的適配器模式、對象的適配器模式、接口的適配器模式。
適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由於接口不匹配所造成的類的兼容性問題。有點抽象,我們來看看詳細的內容。
1.1、類的適配器模式
類的適配器模式核心思想就是:有一個Source類,擁有一個方法,待適配,目標接口是Targetable,通過Adapter類,將Source的功能擴展到Targetable裏。
package com.model.structure;
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
package com.model.structure;
public interface Targetable {
/* 與原類中的方法相同 */
public void method1();
/* 新類的方法 */
public void method2();
}
package com.model.structure;
public class Adapter extends Source implements Targetable {
public void method2() {
System.out.println("this is the targetable method!");
}
}
package com.model.structure;
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
AdapterTest的運行結果:
1.2、對象的適配器模式
對象的適配器模式的基本思路和類的適配器模式相同,只是將Adapter類作修改成Wrapper,這次不繼承Source類,而是持有Source類的實例,以達到解決兼容性的問題。
package com.model.structure;
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();
}
}
package com.model.structure;
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
運行結果跟類的適配器模式例子的一樣。
1.3、接口的適配器模式
接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現類時,必須實現該接口的所有方法,這明顯有時比較浪費,因爲並不是所有的方法都是我們需要的,有時只需要某一些,此處爲了解決這個問題,我們引入了接口的適配器模式,藉助於一個抽象類,該抽象類實現了該接口,實現了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯繫,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行了。
這裏看文字描述已經試夠清楚的了,因此就不貼代碼實例了。
二、裝飾模式
裝飾模式:在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。
裝飾模式的特點:
(1) 裝飾對象和真實對象有相同的接口。這樣客戶端對象就能以和真實對象相同的方式和裝飾對象交互。
(2) 裝飾對象包含一個真實對象的引用(reference)
(3) 裝飾對象接受所有來自客戶端的請求。它把這些請求轉發給真實的對象。
(4) 裝飾對象可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展。繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪。
具體看看代碼實例
package com.model.structure;
public interface Sourceable {
public void method();
}
package com.model.structure;
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
package com.model.structure;
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!");
}
}
package com.model.structure;
public class DecoratorTest {
public static void main(String[] args) {
//(1) 裝飾對象和真實對象有相同的接口。這樣客戶端對象就能以和真實對象相同的方式和裝飾對象交互。
//(2) 裝飾對象包含一個真實對象的引用(reference)
//(3) 裝飾對象接受所有來自客戶端的請求。它把這些請求轉發給真實的對象。
//(4) 裝飾對象可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。
// 在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展。
// 繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪。
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
運行結果:
before decorator!
the original method!
after decorator!
三、代理模式
代理模式就是多一個代理類出來,替原對象進行一些操作。代理類就像中介,它比我們掌握着更多的信息。
具體看看代碼實例。
package com.model.structure;
public interface Sourceable {
public void method();
}
package com.model.structure;
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
package com.model.structure;
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!");
}
}
package com.model.structure;
public class ProxyTest {
public static void main(String[] args) {
Sourceable source = new Proxy();
source.method();
}
}
運行結果:
before proxy!
the original method!
after proxy!
四、外觀模式
外觀模式是爲了解決類與類之間的依賴關係的,像spring一樣,可以將類和類之間的關係配置到配置文件中,而外觀模式就是將他們的關係放在一個Facade類中,降低了類類之間的耦合度,該模式中沒有涉及到接口。
我們以一個計算機的啓動過程爲例,看看如下的代碼:
package com.model.structure;
public class CPU {
public void startup() {
System.out.println("cpu startup!");
}
public void shutdown() {
System.out.println("cpu shutdown!");
}
}
package com.model.structure;
public class Disk {
public void startup() {
System.out.println("disk startup!");
}
public void shutdown() {
System.out.println("disk shutdown!");
}
}
package com.model.structure;
public class Memory {
public void startup() {
System.out.println("memory startup!");
}
public void shutdown() {
System.out.println("memory shutdown!");
}
}
package com.model.structure;
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!");
}
}
package com.model.structure;
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!
五、橋接模式
在軟件系統中,某些類型由於自身的邏輯,它具有兩個或多個維度的變化,那麼如何應對這種“多維度的變化”?如何利用面嚮對象的技術來使得該類型能夠輕鬆的沿着多個方向進行變化,而又不引入額外的複雜度?這就要使用Bridge模式。
在提出橋樑模式的時候指出,橋樑模式的用意是”將抽象化(Abstraction)與實現化(Implementation)脫耦,使得二者可以獨立地變化”。這句話有三個關鍵詞,也就是抽象化、實現化和脫耦。
抽象化:存在於多個實體中的共同的概念性聯繫,就是抽象化。作爲一個過程,抽象化就是忽略一些信息,從而把不同的實體當做同樣的實體對待。
實現化:抽象化給出的具體實現,就是實現化。
脫耦:所謂耦合,就是兩個實體的行爲的某種強關聯。而將它們的強關聯去掉,就是耦合的解脫,或稱脫耦。在這裏,脫耦是指將抽象化和實現化之間的耦合解脫開,或者說是將它們之間的強關聯改換成弱關聯。
下面我們來看看代碼實例:
package com.model.structure;
public interface Driver {
public void connect();
}
package com.model.structure;
public class MysqlDriver implements Driver {
@Override
public void connect() {
System.out.println("connect mysql done!");
}
}
package com.model.structure;
public class DB2Driver implements Driver {
@Override
public void connect() {
System.out.println("connect db2 done!");
}
}
package com.model.structure;
public abstract class DriverManager {
private Driver driver;
public void connect() {
driver.connect();
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
package com.model.structure;
public class MyDriverManager extends DriverManager {
public void connect() {
super.connect();
}
}
package com.model.structure;
public class Client {
public static void main(String[] args) {
DriverManager driverManager = new MyDriverManager();
Driver driver1 = new MysqlDriver();
driverManager.setDriver(driver1);
driverManager.connect();
Driver driver2 = new DB2Driver();
driverManager.setDriver(driver2);
driverManager.connect();
}
}
執行結果:
connect mysql done!
connect db2 done!
如果看完代碼實例還不是很理解,我們想想如下兩個維度擴展:(1)假設我想加一個OracleDriver,這是一個維度,很好理解,不多解釋。(2)假設我們想在連接前後固定輸出點什麼,我們只需要加一個MyDriverManager2,代碼如下:
package com.model.structure;
public class MyDriverManager2 extends DriverManager {
public void connect() {
System.out.println("before connect");
super.connect();
System.out.println("after connect");
}
}
再將Client代碼中的MyDriverManager 改成 MyDriverManager2 ,執行結果如下:
before connect
connect mysql done!
after connect
before connect
connect db2 done!
after connect
六、組合模式
組合模式,將對象組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得用戶對單個對象和組合對象的使用具有一致性。掌握組合模式的重點是要理解清楚 “部分/整體” 還有 ”單個對象“ 與 “組合對象” 的含義。
組合模式讓你可以優化處理遞歸或分級數據結構。
《設計模式》:將對象組合成樹形結構以表示“部分整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
涉及角色:
Component:是組合中的對象聲明接口,在適當的情況下,實現所有類共有接口的默認行爲。聲明一個接口用於訪問和管理Component子部件。
Leaf:在組合中表示葉子結點對象,葉子結點沒有子結點。
Composite:定義有枝節點行爲,用來存儲子部件,在Component接口中實現與子部件有關操作,如增加(add)和刪除(remove)等。
比如現實中公司內各部門的層級關係,請看代碼:
Component:是組合中的對象聲明接口,在適當的情況下,實現所有類共有接口的默認行爲。聲明一個接口用於訪問和管理Component子部件。
package com.model.structure;
public abstract class Company {
private String name;
public Company() {
}
public Company(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected abstract void add(Company company);
protected abstract void romove(Company company);
protected abstract void display(int depth);
}
Composite:定義有枝節點行爲,用來存儲子部件,在Component接口中實現與子部件有關操作,如增加(add)和刪除(remove)等。
package com.model.structure;
import java.util.ArrayList;
import java.util.List;
public class ConcreteCompany extends Company {
private List<Company> cList;
public ConcreteCompany() {
cList = new ArrayList();
}
public ConcreteCompany(String name) {
super(name);
cList = new ArrayList();
}
@Override
protected void add(Company company) {
cList.add(company);
}
@Override
protected void display(int depth) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(new String(sb) + this.getName());
for (Company c : cList) {
c.display(depth + 2);
}
}
@Override
protected void romove(Company company) {
cList.remove(company);
}
}
Leaf:在組合中表示葉子結點對象,葉子結點沒有子結點。
package com.model.structure;
public class HRDepartment extends Company {
public HRDepartment(String name) {
super(name);
}
@Override
protected void add(Company company) {
}
@Override
protected void display(int depth) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(new String(sb) + this.getName());
}
@Override
protected void romove(Company company) {
}
}
package com.model.structure;
public class FinanceDepartment extends Company {
public FinanceDepartment(String name) {
super(name);
}
@Override
protected void add(Company company) {
}
@Override
protected void display(int depth) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(new String(sb) + this.getName());
}
@Override
protected void romove(Company company) {
}
}
Client:
package com.model.structure;
public class Client {
public static void main(String[] args) {
Company root = new ConcreteCompany();
root.setName("北京總公司");
root.add(new HRDepartment("總公司人力資源部"));
root.add(new FinanceDepartment("總公司財務部"));
Company shandongCom = new ConcreteCompany("山東分公司");
shandongCom.add(new HRDepartment("山東分公司人力資源部"));
shandongCom.add(new FinanceDepartment("山東分公司賬務部"));
Company zaozhuangCom = new ConcreteCompany("棗莊辦事處");
zaozhuangCom.add(new FinanceDepartment("棗莊辦事處財務部"));
zaozhuangCom.add(new HRDepartment("棗莊辦事處人力資源部"));
Company jinanCom = new ConcreteCompany("濟南辦事處");
jinanCom.add(new FinanceDepartment("濟南辦事處財務部"));
jinanCom.add(new HRDepartment("濟南辦事處人力資源部"));
shandongCom.add(jinanCom);
shandongCom.add(zaozhuangCom);
Company huadongCom = new ConcreteCompany("上海華東分公司");
huadongCom.add(new HRDepartment("上海華東分公司人力資源部"));
huadongCom.add(new FinanceDepartment("上海華東分公司賬務部"));
Company hangzhouCom = new ConcreteCompany("杭州辦事處");
hangzhouCom.add(new FinanceDepartment("杭州辦事處財務部"));
hangzhouCom.add(new HRDepartment("杭州辦事處人力資源部"));
Company nanjingCom = new ConcreteCompany("南京辦事處");
nanjingCom.add(new FinanceDepartment("南京辦事處財務部"));
nanjingCom.add(new HRDepartment("南京辦事處人力資源部"));
huadongCom.add(hangzhouCom);
huadongCom.add(nanjingCom);
root.add(shandongCom);
root.add(huadongCom);
root.display(0);
}
}
運行結果:
北京總公司
--總公司人力資源部
--總公司財務部
--山東分公司
----山東分公司人力資源部
----山東分公司賬務部
----濟南辦事處
------濟南辦事處財務部
------濟南辦事處人力資源部
----棗莊辦事處
------棗莊辦事處財務部
------棗莊辦事處人力資源部
--上海華東分公司
----上海華東分公司人力資源部
----上海華東分公司賬務部
----杭州辦事處
------杭州辦事處財務部
------杭州辦事處人力資源部
----南京辦事處
------南京辦事處財務部
------南京辦事處人力資源部
七、享元模式
享元模式的主要目的是實現對象的共享,即共享池,當系統中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。
一提到共享池,我們很容易聯想到Java裏面的JDBC連接池,想想每個連接的特點,我們不難總結出:適用於作共享的一些個對象,他們有一些共有的屬性,就拿數據庫連接池來說,url、driverClassName、username、password及dbname,這些屬性對於每個連接來說都是一樣的,所以就適合用享元模式來處理,建一個工廠類,將上述類似屬性作爲內部數據,其它的作爲外部數據,在方法調用時,當做參數傳進來,這樣就節省了空間,減少了實例的數量。
看下數據庫連接池的代碼:
package com.model.structure;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Vector;
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;
/* 構造方法,做一些初始化工作 */
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);
}
/* 返回連接池中的一個數據庫連接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}
}
通過連接池的管理,實現了數據庫連接的共享,不需要每一次都重新創建連接,節省了數據庫重新創建的開銷,提升了系統的性能!
注,本文參考了另外一位博主的文章,某些地方有結合自己的一些理解加以修改:
http://blog.csdn.net/zhangerqing/article/details/8194653