9.設(shè)計模式

1.單例模式

1.1 概念

簡單點說,就是一個應(yīng)用程序中,某個類的實例對象只有一個,你沒有辦法去new,因為構(gòu)造器是被private修飾的,一般通過getInstance()的方法來獲取它們的實例。

getInstance()的返回值是一個對象的引用,并不是一個新的實例,所以不要錯誤的理解成多個對象。

步驟1.
創(chuàng)建一個單例對象:


public class SingleObject {
 
   //創(chuàng)建 SingleObject 的一個對象
   private static SingleObject instance = new SingleObject();
 
   //讓構(gòu)造函數(shù)為 private,這樣該類就不會被實例化
   private SingleObject(){}
 
   //獲取唯一可用的對象
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

步驟2.
從 singleton 類獲取唯一的對象。


public class SingletonPatternDemo {
   public static void main(String[] args) {
 
      //不合法的構(gòu)造函數(shù)
      //編譯時錯誤:構(gòu)造函數(shù) SingleObject() 是不可見的
      //SingleObject object = new SingleObject();
 
      //獲取唯一可用的對象
      SingleObject object = SingleObject.getInstance();
 
      //顯示消息
      object.showMessage();
   }
}
截屏2020-07-16下午6.24.22.png

1.2 單例模式的幾種實現(xiàn)方式

1、懶漢式,線程不安全
描述:這種方式是最基本的實現(xiàn)方式,這種實現(xiàn)最大的問題就是不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。
這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。


public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

2、懶漢式,線程安全
優(yōu)點:第一次調(diào)用才初始化,避免內(nèi)存浪費。
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。
getInstance() 的性能對應(yīng)用程序不是很關(guān)鍵(該方法使用不太頻繁)。


public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

3、餓漢式
描述:這種方式比較常用,但容易產(chǎn)生垃圾對象。
優(yōu)點:沒有加鎖,執(zhí)行效率會提高。
缺點:類加載時就初始化,浪費內(nèi)存。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
描述:這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。


public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

5、登記式/靜態(tài)內(nèi)部類
描述:這種方式能達到雙檢鎖方式一樣的功效,但實現(xiàn)更簡單。對靜態(tài)域使用延遲初始化,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。


public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}

6、枚舉
描述:這種實現(xiàn)方式還沒有被廣泛采用,但這是實現(xiàn)單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

2.觀察者模式

2.1 概念

對象間一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。


截屏2020-07-16下午6.47.31.png

舉個栗子:假設(shè)有三個人,小美(女,22),小王和小李。小美很漂亮,小王和小李是兩個程序猿,時刻關(guān)注著小美的一舉一動。有一天,小美說了一句:“誰來陪我打游戲啊。”這句話被小王和小李聽到了,結(jié)果樂壞了,蹭蹭蹭,沒一會兒,小王就沖到小美家門口了,在這里,小美是被觀察者,小王和小李是觀察者,被觀察者發(fā)出一條信息,然后觀察者們進行相應(yīng)的處理,看代碼:

public interface Person {
   //小王和小李通過這個接口可以接收到小美發(fā)過來的消息
   void getMessage(String s);
}

這個接口相當于小王和小李的電話號碼,小美發(fā)送通知的時候就會撥打getMessage這個電話,撥打電話就是調(diào)用接口,看不懂沒關(guān)系,先往下看

public class LaoWang implements Person {

   private String name = "小王";

   public LaoWang() {
   }

   @Override
   public void getMessage(String s) {
       System.out.println(name + "接到了小美打過來的電話,電話內(nèi)容是:" + s);
   }
}

public class LaoLi implements Person {

   private String name = "小李";

   public LaoLi() {
   }

   @Override
   public void getMessage(String s) {
       System.out.println(name + "接到了小美打過來的電話,電話內(nèi)容是:->" + s);
   }
}

代碼很簡單,我們再看看小美的代碼:

public class XiaoMei {
   List<Person> list = new ArrayList<Person>();
    public XiaoMei(){
    }

    public void addPerson(Person person){
        list.add(person);
    }

    //遍歷list,把自己的通知發(fā)送給所有暗戀自己的人
    public void notifyPerson() {
        for(Person person:list){
            person.getMessage("你們過來吧,誰先過來誰就能陪我一起玩兒游戲!");
        }
    }
}

我們寫一個測試類來看一下結(jié)果對不對

public class Test {
   public static void main(String[] args) {

       XiaoMei xiao_mei = new XiaoMei();
       LaoWang lao_wang = new LaoWang();
       LaoLi lao_li = new LaoLi();

       //小王和小李在小美那里都注冊了一下
       xiao_mei.addPerson(lao_wang);
       xiao_mei.addPerson(lao_li);

       //小美向小王和小李發(fā)送通知
       xiao_mei.notifyPerson();
   }
}

3 裝飾者模式

對已有的業(yè)務(wù)邏輯進一步的封裝,使其增加額外的功能,如Java中的IO流就使用了裝飾者模式,用戶在使用的時候,可以任意組裝,達到自己想要的效果。 舉個栗子,我想吃三明治,首先我需要一根大大的香腸,我喜歡吃奶油,在香腸上面加一點奶油,再放一點蔬菜,最后再用兩片面包夾一下,很豐盛的一頓午飯,營養(yǎng)又健康。(ps:不知道上海哪里有賣好吃的三明治的,求推薦~)那我們應(yīng)該怎么來寫代碼呢? 首先,我們需要寫一個Food類,讓其他所有食物都來繼承這個類,看代碼:

public class Food {

   private String food_name;

   public Food() {
   }

   public Food(String food_name) {
       this.food_name = food_name;
   }

   public String make() {
       return food_name;
   };
}

然后我們寫幾個子類繼承它:

//面包類
public class Bread extends Food {

   private Food basic_food;

   public Bread(Food basic_food) {
       this.basic_food = basic_food;
   }

   public String make() {
       return basic_food.make()+"+面包";
   }
}

//奶油類
public class Cream extends Food {

   private Food basic_food;

   public Cream(Food basic_food) {
       this.basic_food = basic_food;
   }

   public String make() {
       return basic_food.make()+"+奶油";
   }
}

//蔬菜類
public class Vegetable extends Food {

   private Food basic_food;

   public Vegetable(Food basic_food) {
       this.basic_food = basic_food;
   }

   public String make() {
       return basic_food.make()+"+蔬菜";
   }

}

Test類:

public class Test {
   public static void main(String[] args) {
       Food food = new Bread(new Vegetable(new Cream(new Food("香腸"))));
       System.out.println(food.make());
   }
}

4 適配器模式

將兩種完全不同的事物聯(lián)系到一起,就像現(xiàn)實生活中的變壓器。假設(shè)一個手機充電器需要的電壓是20V,但是正常的電壓是220V,這時候就需要一個變壓器,將220V的電壓轉(zhuǎn)換成20V的電壓,這樣,變壓器就將20V的電壓和手機聯(lián)系起來了。

public class Test {
   public static void main(String[] args) {
       Phone phone = new Phone();
       VoltageAdapter adapter = new VoltageAdapter();
       phone.setAdapter(adapter);
       phone.charge();
   }
}

// 手機類
class Phone {

   public static final int V = 220;// 正常電壓220v,是一個常量

   private VoltageAdapter adapter;

   // 充電
   public void charge() {
       adapter.changeVoltage();
   }

   public void setAdapter(VoltageAdapter adapter) {
       this.adapter = adapter;
   }
}

// 變壓器
class VoltageAdapter {
   // 改變電壓的功能
   public void changeVoltage() {
       System.out.println("正在充電...");
       System.out.println("原始電壓:" + Phone.V + "V");
       System.out.println("經(jīng)過變壓器轉(zhuǎn)換之后的電壓:" + (Phone.V - 200) + "V");
   }
}

5 工廠模式

5.1 概念

工廠模式(Factory Pattern)是 Java 中最常用的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

在工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象。

5.2 介紹

意圖:定義一個創(chuàng)建對象的接口,讓其子類自己決定實例化哪一個工廠類,工廠模式使其創(chuàng)建過程延遲到子類進行。

主要解決:主要解決接口選擇的問題。

何時使用:我們明確地計劃不同條件下創(chuàng)建不同實例時。

如何解決:讓其子類實現(xiàn)工廠接口,返回的也是一個抽象的產(chǎn)品。

關(guān)鍵代碼:創(chuàng)建過程在其子類執(zhí)行。

應(yīng)用實例: 1、您需要一輛汽車,可以直接從工廠里面提貨,而不用去管這輛汽車是怎么做出來的,以及這個汽車里面的具體實現(xiàn)。 2、Hibernate 換數(shù)據(jù)庫只需換方言和驅(qū)動就可以。

優(yōu)點: 1、一個調(diào)用者想創(chuàng)建一個對象,只要知道其名稱就可以了。 2、擴展性高,如果想增加一個產(chǎn)品,只要擴展一個工廠類就可以。 3、屏蔽產(chǎn)品的具體實現(xiàn),調(diào)用者只關(guān)心產(chǎn)品的接口。

缺點:每次增加一個產(chǎn)品時,都需要增加一個具體類和對象實現(xiàn)工廠,使得系統(tǒng)中類的個數(shù)成倍增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,同時也增加了系統(tǒng)具體類的依賴。這并不是什么好事。

使用場景: 1、日志記錄器:記錄可能記錄到本地硬盤、系統(tǒng)事件、遠程服務(wù)器等,用戶可以選擇記錄日志到什么地方。 2、數(shù)據(jù)庫訪問,當用戶不知道最后系統(tǒng)采用哪一類數(shù)據(jù)庫,以及數(shù)據(jù)庫可能有變化時。 3、設(shè)計一個連接服務(wù)器的框架,需要三個協(xié)議,"POP3"、"IMAP"、"HTTP",可以把這三個作為產(chǎn)品類,共同實現(xiàn)一個接口。


截屏2020-07-16下午9.07.56.png

5.3 代碼

步驟 1
創(chuàng)建一個接口:

public interface Shape {
   void draw();
}

步驟 2
創(chuàng)建實現(xiàn)接口的實體類。

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}


public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}


public class Circle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

步驟 3
創(chuàng)一個工廠,生成基于給定信息的實體類的對象。

public class ShapeFactory {
    
   //使用 getShape 方法獲取形狀類型的對象
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

步驟 4
使用該工廠,通過傳遞類型信息來獲取實體類的對象。


public class FactoryPatternDemo {
 
   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();
 
      //獲取 Circle 的對象,并調(diào)用它的 draw 方法
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //調(diào)用 Circle 的 draw 方法
      shape1.draw();
 
      //獲取 Rectangle 的對象,并調(diào)用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //調(diào)用 Rectangle 的 draw 方法
      shape2.draw();
 
      //獲取 Square 的對象,并調(diào)用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //調(diào)用 Square 的 draw 方法
      shape3.draw();
   }
}

6 代理模式(proxy)

http://www.itdecent.cn/p/9bcac608c714

7 抽象工廠模式

7.1 概念

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創(chuàng)建其他工廠。該超級工廠又稱為其他工廠的工廠。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

在抽象工廠模式中,接口是負責(zé)創(chuàng)建一個相關(guān)對象的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供對象。

7.2 介紹

意圖:提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定它們具體的類。

主要解決:主要解決接口選擇的問題。

何時使用:系統(tǒng)的產(chǎn)品有多于一個的產(chǎn)品族,而系統(tǒng)只消費其中某一族的產(chǎn)品。

如何解決:在一個產(chǎn)品族里面,定義多個產(chǎn)品。

關(guān)鍵代碼:在一個工廠里聚合多個同類產(chǎn)品。

應(yīng)用實例:工作了,為了參加一些聚會,肯定有兩套或多套衣服吧,比如說有商務(wù)裝(成套,一系列具體產(chǎn)品)、時尚裝(成套,一系列具體產(chǎn)品),甚至對于一個家庭來說,可能有商務(wù)女裝、商務(wù)男裝、時尚女裝、時尚男裝,這些也都是成套的,即一系列具體產(chǎn)品。假設(shè)一種情況(現(xiàn)實中是不存在的,要不然,沒法進入共產(chǎn)主義了,但有利于說明抽象工廠模式),在您的家中,某一個衣柜(具體工廠)只能存放某一種這樣的衣服(成套,一系列具體產(chǎn)品),每次拿這種成套的衣服時也自然要從這個衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具體工廠)都是衣柜類的(抽象工廠)某一個,而每一件成套的衣服又包括具體的上衣(某一具體產(chǎn)品),褲子(某一具體產(chǎn)品),這些具體的上衣其實也都是上衣(抽象產(chǎn)品),具體的褲子也都是褲子(另一個抽象產(chǎn)品)。

優(yōu)點:當一個產(chǎn)品族中的多個對象被設(shè)計成一起工作時,它能保證客戶端始終只使用同一個產(chǎn)品族中的對象。

缺點:產(chǎn)品族擴展非常困難,要增加一個系列的某一產(chǎn)品,既要在抽象的 Creator 里加代碼,又要在具體的里面加代碼。

使用場景: 1、QQ 換皮膚,一整套一起換。 2、生成不同操作系統(tǒng)的程序。

注意事項:產(chǎn)品族難擴展,產(chǎn)品等級易擴展。

截屏2020-07-16下午9.41.41.png

7.3 代碼

步驟 1
為形狀創(chuàng)建一個接口。

public interface Shape {
   void draw();
}

步驟 2
創(chuàng)建實現(xiàn)接口的實體類

class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}
class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}
 class Circle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

步驟 3
為顏色創(chuàng)建一個接口。

public interface Color {
   void fill();
}

步驟4
創(chuàng)建實現(xiàn)接口的實體類。


class Red implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Red::fill() method.");
   }
}

class Green implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Green::fill() method.");
   }
}

class Blue implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Blue::fill() method.");
   }
}

步驟 5
為 Color 和 Shape 對象創(chuàng)建抽象類來獲取工廠。

public abstract class AbstractFactory {
   public abstract Color getColor(String color);
   public abstract Shape getShape(String shape) ;
}

步驟 6
創(chuàng)建擴展了 AbstractFactory 的工廠類,基于給定的信息生成實體類的對象。

public class ShapeFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      return null;
   }
}

public class ColorFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      if(color == null){
         return null;
      }        
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      } else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      } else if(color.equalsIgnoreCase("BLUE")){
         return new Blue();
      }
      return null;
   }
}

步驟 7
創(chuàng)建一個工廠創(chuàng)造器/生成器類,通過傳遞形狀或顏色信息來獲取工廠。


public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      } else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

步驟 8
使用 FactoryProducer 來獲取 AbstractFactory,通過傳遞類型信息來獲取實體類的對象。

public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      } else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

步驟 8
使用 FactoryProducer 來獲取 AbstractFactory,通過傳遞類型信息來獲取實體類的對象。


public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
 
      //獲取形狀工廠
      AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
 
      //獲取形狀為 Circle 的對象
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //調(diào)用 Circle 的 draw 方法
      shape1.draw();
 
      //獲取形狀為 Rectangle 的對象
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //調(diào)用 Rectangle 的 draw 方法
      shape2.draw();
      
      //獲取形狀為 Square 的對象
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //調(diào)用 Square 的 draw 方法
      shape3.draw();
 
      //獲取顏色工廠
      AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
 
      //獲取顏色為 Red 的對象
      Color color1 = colorFactory.getColor("RED");
 
      //調(diào)用 Red 的 fill 方法
      color1.fill();
 
      //獲取顏色為 Green 的對象
      Color color2 = colorFactory.getColor("Green");
 
      //調(diào)用 Green 的 fill 方法
      color2.fill();
 
      //獲取顏色為 Blue 的對象
      Color color3 = colorFactory.getColor("BLUE");
 
      //調(diào)用 Blue 的 fill 方法
      color3.fill();
   }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容