Java中常用的設計模式代碼與理解
一、單例模式
1.餓漢式 (太餓了,類加載的時候就創建實例)
/** * 餓漢式單例模式 */ public class HungrySingleInstance { // 在類加載時生成一個實例 private final static HungrySingleInstance instance = new HungrySingleInstance(); //因為類默認會有一個公有的無參構造,所以用私有的將其覆蓋 private HungrySingleInstance(){} //調用此方法,返回類加載時生成的單例對象 public static HungrySingleInstance getInstance(){ return instance; } }
所謂餓漢式單例設計模式,就是將類的靜態實例作為該類的一個成員變量,也就是說在 JVM 加載它的時候就已經創建了該類的實例,因此它不會存在多線程的安全問題。
但是提前對實例進行了初始化或者說構造,如果此實例又沒有使用到,就會造成資源的浪費。
2.懶漢式 (太懶了,不主動創建實例,當第一次調用時,才創建)
/** * 懶漢式單例模式 */ public class LazySingleInstance { // 在類加載時生成一個引用為null的實例 private static LazySingleInstance instance = null; //因為類默認會有一個公有的無參構造,所以用私有的將其覆蓋 private LazySingleInstance(){ } public static LazySingleInstance getInstance(){ //當實例不為空,就直接返回此實例 if(null == instance){ //鎖+判斷,避免當多個線程同時通過上面那個判斷時,創建多個實例,造成安全問題 synchronized (LazySingleInstance.class){ //只有第一次調這個方法時,創建實例 if(null == instance) { instance = new LazySingleInstance(); } } } return instance; } }
懶漢式單例模式就同時解決了效率與安全的問題。
單例模式常用於以下場景:
1.經常創建又銷毀的對象;
2.創建需要消耗很多資源,又經常使用的對象;
3.常用來訪問數據庫或文件的對象。
二、工廠模式
1.簡單工廠模式
實例:一個簡單的發送短信郵件的工廠模式。
/** * 一個發送接口,用於發送短信或郵件 */ public interface Send { void send(); }
/** * 郵件類實現發送接口,發出郵件 */ public class Mail implements Send{ @Override public void send() { System.out.println("this is a Mail message!"); } }
/** * 短信類實現發送接口 */ public class Sms implements Send{ @Override public void send() { System.out.println("this is a Sms message!"); } }
/** * 發送工廠類 */ public class SendFactory { /** * 根據參數type的不同,生產不同的實例對象 * @param type * @return */ public Send produce(MessageSenderType type){ if(MessageSenderType.SMS.equals(type)){ return new Sms(); }else if(MessageSenderType.MAIL.equals(type)) { return new Mail(); } return null; } }
/** * 發送類型枚舉類 */ public enum MessageSenderType { //短信 SMS, //郵箱 MAIL }
/** * 工廠測試類 */ public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); factory.produce(MessageSenderType.SMS).send(); factory.produce(MessageSenderType.MAIL).send(); } }
簡單工廠模式當需要增加產品時,要修改源碼,破壞ocp原則(對擴展開放,修改封閉)。
2.工廠方法模式
工廠方法模式與簡單工廠模式差不多,區別在於工廠方法模式的工廠類,由多個方法組成,每個方法用來生產一個相應的對象。而簡單工廠模式,是在一個方法中,根據傳入的參數不同也生產不同的對象。所以他們的局限性也都一樣。
3.抽象工廠模式
抽象工廠模式是所有形態的工廠模式中最為抽象和一般性的,抽象工廠模式可以向客戶端提供一個接口,使得客戶端在不必指定產品的具體類型的情況下,創建多個產品族的產品對象。具有比較好的擴展性,但要寫較多的類和接口,前兩種工廠模式理解上會困難一些。
package abstractFactory; public abstract class Apple implements Fruit { public abstract void get(); } package abstractFactory; public abstract class Banana implements Fruit { public abstract void get(); } package abstractFactory; public interface Fruit { public void get(); } package abstractFactory; public interface FruitFactory { //實例化Apple public Fruit getApple(); //實例化Banana public Fruit getBanana(); } package abstractFactory; public class InnerApple extends Apple { @Override public void get() { System.out.println("長在室內的蘋果"); } } package abstractFactory; public class InnerBanana extends Banana { @Override public void get() { System.out.println("長在室內的香蕉"); } } package abstractFactory; public class InnerFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new InnerApple(); } @Override public Fruit getBanana() { return new InnerBanana(); } } package abstractFactory; public class NorthApple extends Apple { @Override public void get() { System.out.println("長在北方的蘋果"); } } package abstractFactory; public class NorthBanana extends Banana { @Override public void get() { System.out.println("長在北方的香蕉"); } } package abstractFactory; public class NorthFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new NorthApple(); } @Override public Fruit getBanana() { return new NorthBanana(); } } package abstractFactory; public class SouthApple extends Apple { @Override public void get() { System.out.println("長在南方的蘋果"); } } package abstractFactory; public class SouthBanana extends Banana { @Override public void get() { System.out.println("長在南方的香蕉"); } } package abstractFactory; public class SouthFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new SouthApple(); } @Override public Fruit getBanana() { return new SouthBanana(); } } //測試類 package abstractFactory; public class MainClass { public static void main(String[] args) { FruitFactory ff = new NorthFruitFactory(); Fruit apple = ff.getApple(); apple.get(); Fruit banana = ff.getBanana(); banana.get(); System.out.println("~~~~~~~~~~~~~~~~~~~~"); FruitFactory bb = new SouthFruitFactory(); Fruit apple2 = bb.getApple(); apple2.get(); Fruit banana2 = bb.getBanana(); banana2.get(); System.out.println("~~~~~~~~~~~~~~~~~~~~"); //比如要增加室內innerApple,InnerBanana FruitFactory cc = new InnerFruitFactory(); Fruit apple3 = cc.getApple(); apple3.get(); Fruit banana3 = cc.getBanana(); banana3.get(); } }
模式中包含的角色及其職責
1.抽象工廠(Creator)角色
抽象工廠模式的核心,包含對多個產品結構的聲明,任何工廠類都必須實現這個接口。(FruitFactory)
2.具體工廠( Concrete Creator)角色
具體工廠類是抽象工廠的一個實現,負責實例化某個產品族中的產品對象。(InnerFruitFactory,NorthFruitFactory,SouthFruitFactory)
3.抽象(Product)角色
抽象模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口。(Fruit)
4.具體產品(Concrete Product)角色
抽象模式所創建的具體實例對象(NorthApple,NorthBanana,SouthApple,SouthBanana,InnerBanana,InnerFruitFactory)
總結:抽象工廠中方法對應產品結構,具體工廠對應產品族。
在Java中,其實Spring的IOC就是一種工廠模式,通過SessionFactory來實現依賴注入。在Spring的Bean工廠中,新對象不是通過new關鍵字來獲取的,而是通過先讀取配置文件,獲取配置文件中配置的類,反射生成對應的對象。
三、代理模式
代理模式是一種常用的設計模式,百度百科中對其定義為:為其他對象提供一個代理以控制對某個對象的訪問。在某些情況下,一個對象不想或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。即代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。代理類與委托類通常會存在關聯關系,通常需要實現同一個接口。一個代理類的對象與一個委托類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。代理模式的思想是為了提供額外的處理或者不同的操作而在實際對象與調用者之間插入一個代理對象。這些額外的操作通常需要與實際對象進行通信。
1.靜態代理
靜態代理:由程序員創建或者由第三方工具生成,再進行編譯;在程序運行之前,代理類的.class文件已經存在了。靜態代理通常只代理一個類,並且要事先知道代理的是什么。示例:假如一個班上的同學要向老師提交作業,但是老師並不直接收每個人的作業,都是通過把作業交給學習委員,再由學習委員將作業轉交給老師。在這里面就是學習委員代理班上學生上交作業,學習委員就是學生的代理。
/** * 學生接口 */ public interface Student { //交作業方法 void submitHomework(); }
/** * Person實現學生類,同時也就需要重寫學生的交作業方法 */ @Data public class Person implements Student{ private String name; public Person(String name){ this.name = name; } @Override public void submitHomework() { System.out.println(name+"交作業啦!"); } }
/** * 學生的代理實現學生接口 */ @Data public class StudentProxy implements Student{ private Person person; //構造方法,將學生對象委托給代理對象 StudentProxy(Student student){ //student.getClass()為構造方法傳入的對象,判斷類是不是相同 if(student.getClass() == Person.class){ //相當於學生給與了學習委員他的屬性 this.person = (Person) student; } } @Override public void submitHomework() { person.submitHomework(); } }
/** * 測試類 */ public class Test { public static void main(String[] args) { //學生自己交作業 Student student = new Person("槑得說"); student.submitHomework(); //學習委員代交作業 Person person = new Person("有得說"); Student student2 = new StudentProxy(person); student2.submitHomework(); } }
可以看到,在代理類的構造方法中我們先聲明了一個學生對象student,然后再將student傳給了代理對象person,最后再由person來執行交作業的方法,這就是代理模式。
其實在測試類中也可以看到,學生是可以直接交作業的,而且還簡單很多,那為什么還需要代理呢?
其實代理模式呢就是在訪問實際對象時,引入了一定的間接性,不直接調用實際對象的方法,而是通過代理對象來完成。這樣可以降低系統的耦合性,在上述例子中,如果老師還需要做出評價的話,就可以在不改動源碼的情況下,在代理類中完成。
/** * 學生的代理實現學生接口 */ @Data public class StudentProxy implements Student{ private Person zhangsan; //構造方法,將學生對象委托給代理對象 StudentProxy(Student student){ //student.getClass()為構造方法傳入的對象,判斷類是不是相同 if(student.getClass() == Person.class){ //相當於學生給與了學習委員他的屬性 this.zhangsan = (Person) student; } } @Override public void submitHomework() { zhangsan.submitHomework(); System.out.println(zhangsan.getName()+"的作業完成很好!"); } }
代理模式其優點在於:
- 代理模式實現使用者與真實處理者的分離,降低系統的耦合度;
- 調用接口時,便於擴展一些業務無關的其他操作,不影響原系統。
缺點在於:增加類代理角色,性能上比直接使用低。
2.動態代理
Java動態代理的基本原理為:被代理對象需要實現某個接口(這是前提),代理對象會攔截對被代理對象的方法調用,在其中可以全然拋棄被代理對象的方法實現而完成另外的功能,也可以在被代理對象方法調用的前后增加一些額外的功能。動態代理可以為其他對象提供一個代理以控制對某個對象的訪問。代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。