Java 中一般認為有23種設計模式,當然暫時不需要所有的都會,但是其中常見的幾種設計模式應該去掌握。
總體來說設計模式分為三大類:
創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
1. 單例模式
所謂的單例設計指的是一個類只允許產生一個實例化對象。
最好理解的一種設計模式,分為懶漢式和餓漢式。
1.餓漢式
——構造方法私有化,外部無法產生新的實例化對象,只能通過static方法取得實例化對象
class Singleton { /** * 在類的內部可以訪問私有結構,所以可以在類的內部產生實例化對象 */ private static Singleton instance = new Singleton(); /** * private 聲明構造 */ private Singleton() { } /** * 返回對象實例 */ public static Singleton getInstance() { return instance; } public void print() { System.out.println("Hello Singleton..."); } }
2.懶漢式
——當第一次去使用Singleton對象的時候才會為其產生實例化對象的操作
class Singleton { /** * 聲明變量 */ private static volatile Singleton singleton = null; /** * 私有構造方法 */ private Singleton() { } /** * 提供對外方法 * @return */ public static Singleton getInstance() { // 還未實例化 if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } public void print() { System.out.println("Hello World"); } }
當多個線程並發執行 getInstance 方法時,懶漢式會存在線程安全問題,所以用到了 synchronized 來實現線程的同步,當一個線程獲得鎖的時候其他線程就只能在外等待其執行完畢。
而餓漢式則不存在線程安全的問題。
2. 工廠設計模式
工廠模式分為工廠方法模式和抽象工廠模式。
工廠方法模式
工廠方法模式: 1. 工廠方法模式分為三種:普通工廠模式,就是建立一個工廠類,對實現了同一接口的一些類進行實例的創建。 2. 多個工廠方法模式,是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。 3. 靜態工廠方法模式,將上面的多個工廠方法模式里的方法置為靜態的,不需要創建實例,直接調用即可。
1. 普通工廠模式
建立一個工廠類,對實現了同一接口的一些類進行實例的創建。
interface Sender { void Send(); } class MailSender implements Sender { @Override public void Send() { System.out.println("This is mail sender..."); } } class SmsSender implements Sender { @Override public void Send() { System.out.println("This is sms sender..."); } } public class FactoryPattern { public static void main(String[] args) { Sender sender = produce("mail"); sender.Send(); } public static Sender produce(String str) { if ("mail".equals(str)) { return new MailSender(); } else if ("sms".equals(str)) { return new SmsSender(); } else { System.out.println("輸入錯誤..."); return null; } } }
2. 多個工廠方法模式
該模式是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。
interface Sender { void Send(); } class MailSender implements Sender { @Override public void Send() { System.out.println("This is mail sender..."); } } class SmsSender implements Sender { @Override public void Send() { System.out.println("This is sms sender..."); } } class SendFactory { public Sender produceMail() { return new MailSender(); } public Sender produceSms() { return new SmsSender(); } } public class FactoryPattern { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produceMail(); sender.Send(); } }
3. 靜態工廠方法模式
將上面的多個工廠方法模式里的方法置為靜態的,不需要創建實例,直接調用即可。
interface Sender { void Send(); } class MailSender implements Sender { @Override public void Send() { System.out.println("This is mail sender..."); } } class SmsSender implements Sender { @Override public void Send() { System.out.println("This is sms sender..."); } } class SendFactory { public static Sender produceMail() { return new MailSender(); } public static Sender produceSms() { return new SmsSender(); } } public class FactoryPattern { public static void main(String[] args) { Sender sender = SendFactory.produceMail(); sender.Send(); } }
抽象工廠模式
工廠方法模式有一個問題就是,類的創建依賴工廠類,也就是說,如果想要擴展程序,必須對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?
那么這就用到了抽象工廠模式,創建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。
interface Provider { Sender produce(); } interface Sender { void Send(); } class MailSender implements Sender { public void Send() { System.out.println("This is mail sender..."); } } class SmsSender implements Sender { public void Send() { System.out.println("This is sms sender..."); } } class SendMailFactory implements Provider { public Sender produce() { return new MailSender(); } } class SendSmsFactory implements Provider { public Sender produce() { return new SmsSender(); } } public class FactoryPattern { public static void main(String[] args) { Provider provider = new SendMailFactory(); Sender sender = provider.produce(); sender.Send(); } }
3. 建造者模式
工廠類模式提供的是創建單個類的模式,而建造者模式則是將各種產品集中起來管理,用來創建復合對象,所謂復合對象就是指某個類具有不同的屬性。
import java.util.ArrayList; import java.util.List; /** * @Author: LiuWang * @Created: 2018/8/6 17:47 */ abstract class Builder { /** * 第一步:裝CPU */ public abstract void buildCPU(); /** * 第二步:裝主板 */ public abstract void buildMainBoard(); /** * 第三步:裝硬盤 */ public abstract void buildHD(); /** * 獲得組裝好的電腦 * @return */ public abstract Computer getComputer(); } /** * 裝機人員裝機 */ class Director { public void Construct(Builder builder) { builder.buildCPU(); builder.buildMainBoard(); builder.buildHD(); } } /** * 具體的裝機人員 */ class ConcreteBuilder extends Builder { Computer computer = new Computer(); @Override public void buildCPU() { computer.Add("裝CPU"); } @Override public void buildMainBoard() { computer.Add("裝主板"); } @Override public void buildHD() { computer.Add("裝硬盤"); } @Override public Computer getComputer() { return computer; } } class Computer { /** * 電腦組件集合 */ private List<String> parts = new ArrayList<String>(); public void Add(String part) { parts.add(part); } public void print() { for (int i = 0; i < parts.size(); i++) { System.out.println("組件:" + parts.get(i) + "裝好了..."); } System.out.println("電腦組裝完畢..."); } } public class BuilderPattern { public static void main(String[] args) { Director director = new Director(); Builder builder = new ConcreteBuilder(); director.Construct(builder); Computer computer = builder.getComputer(); computer.print(); } }
4. 適配器設計模式
適配器模式是將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由於接口不匹配所造成的的類的兼容性問題。
主要分三類:類的適配器模式、對象的適配器模式、接口的適配器模式。
1. 類的適配器模式:
class Source { public void method1() { System.out.println("This is original method..."); } } interface Targetable { /** * 與原類中的方法相同 */ public void method1(); /** * 新類的方法 */ public void method2(); } class Adapter extends Source implements Targetable { @Override public void method2() { System.out.println("This is the targetable method..."); } } public class AdapterPattern { public static void main(String[] args) { Targetable targetable = new Adapter(); targetable.method1(); targetable.method2(); } }
2. 對象的適配器模式
基本思路和類的適配器模式相同,只是將Adapter 類作修改,這次不繼承Source 類,而是持有Source 類的實例,以達到解決兼容性的問題。
class Source { public void method1() { System.out.println("This is original method..."); } } interface Targetable { /** * 與原類中的方法相同 */ public void method1(); /** * 新類的方法 */ public void method2(); } class Wrapper implements Targetable { private Source source; public Wrapper(Source source) { super(); this.source = source; } @Override public void method1() { source.method1(); } @Override public void method2() { System.out.println("This is the targetable method..."); } } public class AdapterPattern { public static void main(String[] args) { Source source = new Source(); Targetable targetable = new Wrapper(source); targetable.method1(); targetable.method2(); } }
3. 接口的適配器模式
接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現類時,必須實現該接口的所有方法,這明顯有時比較浪費,因為並不是所有的方法都是我們需要的,有時只需要某一些,此處為了解決這個問題,我們引入了接口的適配器模式,借助於一個抽象類,該抽象類實現了該接口,實現了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯系,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行。
/** * 定義端口接口,提供通信服務 */ interface Port { /** * 遠程SSH端口為22 */ void SSH(); /** * 網絡端口為80 */ void NET(); /** * Tomcat容器端口為8080 */ void Tomcat(); /** * MySQL數據庫端口為3306 */ void MySQL(); } /** * 定義抽象類實現端口接口,但是什么事情都不做 */ abstract class Wrapper implements Port { @Override public void SSH() { } @Override public void NET() { } @Override public void Tomcat() { } @Override public void MySQL() { } } /** * 提供聊天服務 * 需要網絡功能 */ class Chat extends Wrapper { @Override public void NET() { System.out.println("Hello World..."); } } /** * 網站服務器 * 需要Tomcat容器,Mysql數據庫,網絡服務,遠程服務 */ class Server extends Wrapper { @Override public void SSH() { System.out.println("Connect success..."); } @Override public void NET() { System.out.println("WWW..."); } @Override public void Tomcat() { System.out.println("Tomcat is running..."); } @Override public void MySQL() { System.out.println("MySQL is running..."); } } public class AdapterPattern { private static Port chatPort = new Chat(); private static Port serverPort = new Server(); public static void main(String[] args) { // 聊天服務 chatPort.NET(); // 服務器 serverPort.SSH(); serverPort.NET(); serverPort.Tomcat(); serverPort.MySQL(); } }
5. 裝飾模式
顧名思義,裝飾模式就是給一個對象增加一些新的功能,而且是動態的,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例。
interface Shape { void draw(); } /** * 實現接口的實體類 */ class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle..."); } } class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle..."); } } /** * 創建實現了 Shape 接口的抽象裝飾類。 */ abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape) { this.decoratedShape = decoratedShape; } @Override public void draw() { decoratedShape.draw(); } } /** * 創建擴展自 ShapeDecorator 類的實體裝飾類。 */ class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); } @Override public void draw() { decoratedShape.draw(); setRedBorder(decoratedShape); } private void setRedBorder(Shape decoratedShape) { System.out.println("Border Color: Red"); } } /** * 使用 RedShapeDecorator 來裝飾 Shape 對象。 */ public class DecoratorPattern { public static void main(String[] args) { Shape circle = new Circle(); Shape redCircle = new RedShapeDecorator(new Circle()); Shape redRectangle = new RedShapeDecorator(new Rectangle()); System.out.println("Circle with normal border"); circle.draw(); System.out.println("\nCircle of red border"); redCircle.draw(); System.out.println("\nRectangle of red border"); redRectangle.draw(); } }
6. 策略模式
策略模式定義了一系列算法,並將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。需要設計一個接口,為一系列實現類提供統一的方法,多個實現類實現該接口,設計一個抽象類(可有可無,屬於輔助類),提供輔助函數。策略模式的決定權在用戶,系統本身提供不同算法的實現,新增或者刪除算法,對各種算法做封裝。因此,策略模式多用在算法決策系統中,外部用戶只需要決定用哪個算法即可。
/** * 抽象算法的策略類,定義所有支持的算法的公共接口 */ abstract class Strategy { /** * 算法方法 */ public abstract void AlgorithmInterface(); } /** * 具體算法A */ class ConcreteStrategyA extends Strategy { //算法A實現方法 @Override public void AlgorithmInterface() { System.out.println("算法A的實現"); } } /** * 具體算法B */ class ConcreteStrategyB extends Strategy { /** * 算法B實現方法 */ @Override public void AlgorithmInterface() { System.out.println("算法B的實現"); } } /** * 具體算法C */ class ConcreteStrategyC extends Strategy { @Override public void AlgorithmInterface() { System.out.println("算法C的實現"); } } /** * 上下文,維護一個對策略類對象的引用 */ class Context { Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void contextInterface(){ strategy.AlgorithmInterface(); } } /** * 客戶端代碼:實現不同的策略 */ public class StrategyPattern { public static void main(String[] args) { Context context; context = new Context(new ConcreteStrategyA()); context.contextInterface(); context = new Context(new ConcreteStrategyB()); context.contextInterface(); context = new Context(new ConcreteStrategyC()); context.contextInterface(); } }
7. 代理模式
代理模式指給一個對象提供一個代理對象,並由代理對象控制對原對象的引用。代理可以分為靜態代理和動態代理。
通過代理模式,可以利用代理對象為被代理對象添加額外的功能,以此來拓展被代理對象的功能。可以用於計算某個方法執行時間,在某個方法執行前后記錄日志等操作。
1. 靜態代理
靜態代理需要我們寫出代理類和被代理類,而且一個代理類和一個被代理類一一對應。代理類和被代理類需要實現同一個接口,通過聚合使得代理對象中有被代理對象的引用,以此實現代理對象控制被代理對象的目的。
/** * 代理類和被代理類共同實現的接口 */ interface IService { void service(); } /** * 被代理類 */ class Service implements IService{ @Override public void service() { System.out.println("被代理對象執行相關操作"); } } /** * 代理類 */ class ProxyService implements IService{ /** * 持有被代理對象的引用 */ private IService service; /** * 默認代理Service類 */ public ProxyService() { this.service = new Service(); } /** * 也可以代理實現相同接口的其他類 * @param service */ public ProxyService(IService service) { this.service = service; } @Override public void service() { System.out.println("開始執行service()方法"); service.service(); System.out.println("service()方法執行完畢"); } } //測試類 public class ProxyPattern { public static void main(String[] args) { IService service = new Service(); //傳入被代理類的對象 ProxyService proxyService = new ProxyService(service); proxyService.service(); } }
2. 動態代理
JDK 1.3 之后,Java通過java.lang.reflect包中的三個類Proxy、InvocationHandler、Method來支持動態代理。動態代理常用於有若干個被代理的對象,且為每個被代理對象添加的功能是相同的(例如在每個方法運行前后記錄日志)。
動態代理的代理類不需要我們編寫,由Java自動產生代理類源代碼並進行編譯最后生成代理對象。
創建動態代理對象的步驟:
1. 指明一系列的接口來創建一個代理對象
2. 創建一個調用處理器(InvocationHandler)對象
3. 將這個代理指定為某個其他對象的代理對象
4. 在調用處理器的invoke()方法中采取代理,一方面將調用傳遞給真實對象,另一方面執行各種需要的操作
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代理類和被代理類共同實現的接口 */ interface IService { void service(); } class Service implements IService{ @Override public void service() { System.out.println("被代理對象執行相關操作"); } } class ServiceInvocationHandler implements InvocationHandler { /** * 被代理的對象 */ private Object srcObject; public ServiceInvocationHandler(Object srcObject) { this.srcObject = srcObject; } @Override public Object invoke(Object proxyObj, Method method, Object[] args) throws Throwable { System.out.println("開始執行"+method.getName()+"方法"); //執行原對象的相關操作,容易忘記 Object returnObj = method.invoke(srcObject,args); System.out.println(method.getName()+"方法執行完畢"); return returnObj; } } public class ProxyPattern { public static void main(String[] args) { IService service = new Service(); Class<? extends IService> clazz = service.getClass(); IService proxyService = (IService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new ServiceInvocationHandler(service)); proxyService.service(); } }