23種設計模式的簡單實現(上 1-12)

    設計模式是軟件開發人員在軟件開發過程中面臨的一般問題的解決方案。這些解決方案是衆多軟件開發人員經過相當長的一段時間的試驗和錯誤總結出來的。本文將對23種設計模式每個都進行一個簡單的說明和實現。

    設計模式很大程度上是面向對象設計原則的體現,首先來看一下這些oop設計原則:


創建類的設計模式

一、工廠方法模式

    工廠父類(接口)負責定義產品對象的公共接口,而子類工廠則負責創建具體的產品對象。是爲了把產品的實例化。

代碼例子,我們需要生產Bike和Bus,都會run,但是run的方式不一樣:

package 工廠方法模式;

interface Car {
    void run();
}
class Bike implements Car {
    @Override
    public void run() {
        System.out.println("自行車run");
    }
}
class Bus implements Car {
    @Override
    public void run() {
        System.out.println("公交車run");
    }
}
//定義父類工廠接口
interface ICarFactory {
    Car getCar();
}
//Bike子類工廠接口
class BikeFactory implements ICarFactory {
    @Override
    public Car getCar() {
        return new Bike();
    }
}
//Bus子類工廠接口
class BusFactory implements ICarFactory {
    @Override
    public Car getCar() {
        return new Bus();
    }
}
public class Main {
    public static void main(String[] args) {
        //創建Bike工廠
        ICarFactory bikeFactory = new BikeFactory();
        //獲取Bike實例
        Car bike = bikeFactory.getCar();
        bike.run();//自行車run
        
        //創建Bus工廠
        ICarFactory busFactory = new BusFactory();
        //獲取Bus實例
        Car bus = busFactory.getCar();
        bus.run();//公交車run
    }
}

優點:解耦、靈活、類層次清晰、擴展性好(增加或者修改一個子工廠類就可以修改產品)、屏蔽了產品類,不需要調用者關心產品是如何生產的、迪米特法則,依賴倒置原則,里氏替換原則的體現。

缺點:增加了一個類層次。


二、建造者模式

    將一個複雜對象的構建與表示分離,使得同樣的構建過程可以創建不同的表示。

代碼例子,我們需要構建一個MyClass對象,這個對象的屬性有很多,attr1,attr2,attr3...,爲了節省篇幅,我們只寫三個。而且這些屬性中的某些還有默認值,我們如果使用構造方法重載實現會特別複雜,而且代碼不易讀。如果使用getter/setter也會導致代碼出現長篇幅的getter/setter。我們使用構建者模式解決這個問題:

package 構建器模式;

class MyClass {
    private int attr1;
    private int attr2;
    private int attr3;
    public MyClass(Builder builder){
        attr1 = builder.attr1;
        attr2 = builder.attr2;
        attr3 = builder.attr3;
    }
    //構建器
    static class Builder {
        private final int attr1;
        private int attr2 = 2;
        private int attr3 = 3;
        //修改屬性 返回this
        Builder(int attr1){
            this.attr1 = attr1;
        }
        public Builder attr2(int attr2){
            this.attr2 = attr2;
            return this;
        }
        public Builder attr3(int attr3){
            this.attr3 = attr3;
            return this;
        }
        //調用MyClass構造函數傳入Builder對象構建 返回MyClass對象
        public MyClass Build(){
            return new MyClass(this);
        }
    }
    @Override
    public String toString(){
        return "attr1:"+attr1+" attr2:"+attr2+" attr3:"+attr3;
    }
}
public class Main {
    public static void main(String[] args) {
        //設定必要的參數attr1爲1,其他的爲默認值
        MyClass obj1 = new MyClass.Builder(1).Build();
        System.out.println(obj1);//attr1:1 attr2:2 attr3:3
        //設定必要的參數attr1爲1,其他的參數也對應做出修改
        MyClass obj2 = new MyClass.Builder(1).attr2(20).attr3(30).Build();
        System.out.println(obj2);//attr1:1 attr2:20 attr3:30
    }
}

    這樣代碼顯然在構建過程中更加易讀,沒有長篇的getter/setter,也不需要記重載參數的順序和個數(比如子中obj2的構建過程attr3()方法和attr2()方法的調用可以更改順序)。

使用建造者模式的好處:
1.使用建造者模式可以使客戶端不必知道產品內部組成的細節。
2.具體的建造者類之間是相互獨立的,對系統的擴展非常有利。
3.由於具體的建造者是獨立的,因此可以對建造過程逐步細化,而不對其他的模塊產生任何影響。
使用建造者模式的場合:
1.創建一些複雜的對象時,這些對象的內部組成構件間的建造順序是穩定的,但是對象的內部組成構件面臨着複雜的變化。
2.要創建的複雜對象的算法,獨立於該對象的組成部分,也獨立於組成部分的裝配方法時。


三、抽象工廠模式

提供一個創建一系列或相關依賴對象的接口,而無需指定他們具體的類。

抽象工廠模式除了具有工廠方法模式的優點外,最主要的優點就是可以在類的內部對產品族進行約束

代碼例子,我們需要A和B兩種槍和子彈,一種槍不能用另一種子彈:

package 抽象工廠模式;
//子彈接口
interface Bullet {
    
}
class ABullet implements Bullet {
    
}
class BBullet implements Bullet {
    
}
//槍接口
interface Gun {
    void shot(Bullet bullet);
}
class AGun implements Gun {
    @Override
    public void shot(Bullet bullet) {
        System.out.println("AGun shot with Abullet");
    }
}
class BGun implements Gun {
    @Override
    public void shot(Bullet bullet) {
        System.out.println("BGun shot with Bbullet");
    }
}
//武器工廠
interface WeaponFactory {
    Gun getGun();
    Bullet getBullet();
}
class AWeaponFactory implements WeaponFactory {
    @Override
    public Gun getGun() {
        return new AGun();
    }
    @Override
    public Bullet getBullet() {
        return new ABullet();
    }
}
class BWeaponFactory implements WeaponFactory {
    @Override
    public Gun getGun() {
        return new BGun();
    }
    @Override
    public Bullet getBullet() {
        return new BBullet();
    }
}
public class Main {
    public static void main(String[] args) {
        //創建A類武器工廠 創建A類產品族
        AWeaponFactory aWeaponFactory = new AWeaponFactory();
        Bullet aBullet = aWeaponFactory.getBullet();
        Gun aGun = aWeaponFactory.getGun();
        aGun.shot(aBullet);
        //創建B類武器工廠 創建B類產品族
        BWeaponFactory bWeaponFactory = new BWeaponFactory();
        Bullet bBullet = bWeaponFactory.getBullet();
        Gun bGun = bWeaponFactory.getGun();
        bGun.shot(bBullet);
    }
}

我們有兩個產品族,保證同一種工廠只能生產對應的產品。

當需要創建的對象是一系列相互關聯或相互依賴的產品族時,便可以使用抽象工廠模式。


四、單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

讓類自身負責保存它的唯一實例,這個類可以保證沒有其他實例可以被創建。

代碼例子,使用三種方式創建單例:

package 單例模式;

class Singleton1 {
    private static Singleton1 instance = new Singleton1();
    private Singleton1(){
        
    }
    public static Singleton1 getInstance(){
        return instance;
    }
    public void run(){
        System.out.println("單例運行");
    }
}
class Singleton2 {
    private static volatile Singleton2 instance ;
    private Singleton2(){
        
    }
    public static Singleton2 getInstance(){
        if(instance == null){
            synchronized(Singleton2.class){
                instance = new Singleton2();
            }
        }
        return instance;
    }
    public void run(){
        System.out.println("單例運行");
    }
}
enum Singleton3 {
    INSTANCE;
    //enum的構造函數只支持private 而且因爲所有枚舉都繼承自java.lang.Enum類 因此枚舉不能再繼承其他類 但是可以實現接口
    private Singleton3(){
        
    }
    public void run(){
        System.out.println("單例運行");
    }
}
public class Main {
    public static void main(String[] args) {
        Singleton1.getInstance().run();
        Singleton2.getInstance().run();
        Singleton3.INSTANCE.run();
    }
}

我們分別使用餓加載、線程安全的懶加載、枚舉來實現單例,都是線程安全的。


五、原型模式

用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象
使用原型模式創建對象比直接new一個對象在性能上要好的多,因爲Object類的clone方法是一個本地方法,
它直接操作內存中的二進制流,特別是複製大對象時,性能的差別非常明顯。

使用原型模式的另一個好處是簡化對象的創建,使得創建對象就像我們在編輯文檔時的複製粘貼一樣簡單。

因爲以上優點,所以在需要重複地創建相似對象時可以考慮使用原型模式。比如需要在一個循環體內創建對象,假如對象創建過程比較複雜或者循環次數很多的話,使用原型模式不但可以簡化創建過程,而且可以使系統的整體性能提高很多。

代碼例子,對MyClass對象的克隆。Java中clone()已經是Object類的方法,想要重寫clone()只需要實現Cloneable這個標記接口,但是注意深拷貝和淺拷貝,這裏只是談論設計模式思想,由於篇幅問題不討論這個問題了:

package 原型模式;

class MyClass implements Cloneable {
    private int attr1;
    private int attr2;
    public MyClass(int attr1,int attr2){
        this.attr1 = attr1;
        this.attr2 = attr2;
    }
    @Override
    public MyClass clone(){
        MyClass obj = null;
        try {
            obj = (MyClass)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return obj;
    } 
    @Override
    public String toString(){
        return "attr1:"+attr1+" attr2:"+attr2;
    }
}
public class Main {
    public static void main(String[] args) {
        MyClass mc1 = new MyClass(1,2);
        MyClass mc2 = mc1.clone();
        System.out.println(mc2);//attr1:1 attr2:2
    }
}

通過克隆方法所創建的對象是全新的對象,它們在內存中擁有新的地址,通常對克隆所產生的對象進行修改對原型對象不會造成任何影響,每一個克隆對象都是相互獨立的。


結構類的設計模式

六、適配器模式

將一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。

package 適配器模式;

interface Interface1 {
    void method1();
    void method2();
}
class Class1 {
    public void method1(){
        System.out.println("this is method1");
    }
}
//Class2就是一個Class1和Interface1之間的適配器 這種模式是類適配器模式
class Class2 extends Class1 implements Interface1 {
    //method1通過繼承得到了 重寫method2即可
    @Override
    public void method2() {
        System.out.println("this is method2");
    }
}
//Class3也是一個Class1和Interface1之間的適配器 只不過是通過Class1的實例進行了method1的適配 叫對象適配器模式
class Class3 implements Interface1 {
    private static final Class1 proxy = new Class1();
    @Override
    public void method1() {
        proxy.method1();
    }
    @Override
    public void method2() {
        System.out.println("this is method2");
    }
}
public class Main {
    public static void main(String[] args) {
        Class2 obj1 = new Class2();
        obj1.method1();
        obj1.method2();
        Class3 obj2 = new Class3();
        obj2.method1();
        obj2.method2();
    }
}

適配方法有兩種,類適配和對象適配,類適配是通過繼承類A再實現接口B來實現A和B之間的適配;而對象適配是通過實現接口B,然後使用A對象作爲成員變量進行代理實現A和B的適配。

優點:
通過適配器,客戶端可以調用同一接口,因而對客戶端來說是透明的。這樣做更簡單、更直接、更緊湊。
複用了現存的類,解決了現存類和複用環境要求不一致的問題。
將目標類和適配者類解耦,通過引入一個適配器類重用現有的適配者類,而無需修改原有代碼。
一個對象適配器可以把多個不同的適配者類適配到同一個目標,也就是說,
同一個適配器可以把適配者類和它的子類都適配到目標接口。
缺點:

對於對象適配器來說,更換適配器的實現過程比較複雜。


七、橋接模式

將抽象與實現解耦,使得兩者可以獨立地變化。

代碼例子,設想現在我們需要紅色直線、綠色方框、黑色三角形、紅色方框、紅色三角形....如果我們按照傳統的設計思路,相當於一個顏色的形狀對應了一個類,如果有3種顏色3種形狀就是9個類。如果我們使用橋樑模式,將形狀和顏色相分離,使用繼承/組合的方式對應一個類,就只需要3種顏色類和3種形狀類即可:

package 橋樑模式;

interface Printer {
    void print();
}
class BlackPrinter implements Printer {
    @Override
    public void print() {
        System.out.println("BlackPrinter is printing...");
    }
}
class RedPrinter implements Printer {
    @Override
    public void print() {
        System.out.println("RedPrinter is printing...");
    }
}
class GreenPrinter implements Printer {
    @Override
    public void print() {
        System.out.println("GreenPrinter is printing...");
    }
}
abstract class Shape {
    protected Printer printer;
    Shape(Printer printer){
        this.printer = printer;
    }
    public void draw(){
        printer.print();
    }
}
class Line extends Shape {
    private int distance;
    Line(Printer printer) {
        super(printer);
    }
    Line(Printer printer,int distance) {
        super(printer);
        this.distance = distance;
    }
    @Override
    public void draw(){
        printer.print();
        System.out.println("The "+distance+" distance line has been drawed");
    }
}
class Triangle extends Shape {
    private int a,b,c;
    Triangle(Printer printer) {
        super(printer);
    }
    Triangle(Printer printer,int a,int b,int c) {
        super(printer);
        this.a = a;
        this.b = b;
        this.c = c;
    }
    @Override
    public void draw(){
        printer.print();
        System.out.println("The "+a+" "+b+" "+c+" triangle has been drawed");
    }
}
class Square extends Shape {
    private int a;
    Square(Printer printer) {
        super(printer);
    }
    Square(Printer printer,int a) {
        super(printer);
        this.a = a;
    }
    @Override
    public void draw(){
        printer.print();
        System.out.println("The "+a+" square has been drawed");
    }
}
public class Main {
    public static void main(String[] args) {
        //紅方形獲取
        Printer redPrinter = new RedPrinter();
        Shape redSquare = new Square(redPrinter,1);
        redSquare.draw();//RedPrinter is printing...
        //                 The 1 square has been drawed
        //黑直線獲取
        Printer blackPrinter = new BlackPrinter();
        Shape blackLine = new Line(blackPrinter,1);
        blackLine.draw();//BlackPrinter is printing...
        //                 The 1 distance line has been drawed
        //綠三角形獲取
        Printer greenPrinter = new GreenPrinter();
        Shape greenTriangle = new Triangle(greenPrinter,1,1,1);
        greenTriangle.draw();//GreenPrinter is printing...
        //                     The 1 1 1 triangle has been drawed
    }
}

上面的代碼就是利用組合實現了橋接,減少了類的個數。

橋接模式的使用場景:

1、當一個對象有多個變化因素的時候,通過抽象這些變化因素,將依賴具體實現,修改爲依賴抽象。
2、當某個變化因素在多個對象中共享時。我們可以抽象出這個變化因素,然後實現這些不同的變化因素。

3、當我們期望一個對象的多個變化因素可以動態的變化,而且不影響客戶的程序的使用時。


八、裝飾者模式

動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾模式相比生成子類更加靈活。

優點:

1、對於擴展一個對象的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。
2、可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的具體裝飾類,從而實現不同的行爲。
3、可以對一個對象進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行爲的組合,得到功能更爲強大的對象。

4、具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,原有類庫代碼無須改變,符合“開閉原則”。

缺點:

1、使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在於它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,大量小對象的產生勢必會佔用更多的系統資源,在一定程序上影響程序的性能。

2、裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味着比繼承更加易於出錯,排錯也很困難,對於多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較爲繁瑣。

裝飾者模式有以下角色:

● Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明瞭在具體構件中實現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之後的對象,實現客戶端的透明操作。
● ConcreteComponent(具體構件):它是抽象構件類的子類,用於定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。
● Decorator(抽象裝飾類):它也是抽象構件類的子類,用於給具體構件增加職責,但是具體職責在其子類中實現。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前構件對象的方法,並通過其子類擴展該方法,以達到裝飾的目的。
● ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件添加新的職責。每一個具體裝飾類都定義了一些新的行爲,它可以調用在抽象裝飾類中定義的方法,並可以增加新的方法用以擴充對象的行爲。

代碼例子,設想我們需要一個人,搭配各種而樣的衣服,如果每個穿着搭配都需要創建一個類必然會有很多的類,這裏我們就可以使用裝飾者模式。將人作爲被裝飾的類,而各種各樣的衣服作爲裝飾者:

package 裝飾者模式;

//被裝飾對象基類
abstract class Person {
    protected String description = "null";
    public String getDescription(){
        return description;
    }
    public abstract double cost(); 
}
//具體被裝飾對象
class Man extends Person {
    public Man(){
        description = "Shopping List:";
    }
    @Override
    public double cost() {
        // cost nothing
        return 0;
    }
}
//裝飾者抽象類
abstract class HatDecorator extends Person {
    protected Person person;
    public HatDecorator(Person person){
        this.person = person;
    }
}
abstract class ShoesDecorator extends Person {
    protected Person person;
    public ShoesDecorator(Person person){
        this.person = person;
    }
}
//具體的裝飾者
class GreenHatDecorator extends HatDecorator {
    public GreenHatDecorator(Person person) {
        super(person);
    }
    @Override
    public double cost() {
        return 90+person.cost();
    }
    @Override
    public String getDescription() {
        return person.getDescription()+" a greent hat";
    }
}
class BlackShoesDecorator extends ShoesDecorator {
    public BlackShoesDecorator(Person person) {
        super(person);
    }
    @Override
    public double cost() {
        return 80+person.cost();
    }
    @Override
    public String getDescription() {
        return person.getDescription()+" black shoes";
    }
}
public class Main {
    public static void main(String[] args) {
        Person person = new Man();
        person = new GreenHatDecorator(person);//戴上綠帽
        System.out.println(person.getDescription()+"¥"+person.cost());////Shopping List: a greent hat¥90.0
        person = new BlackShoesDecorator(person);//穿上黑鞋
        System.out.println(person.getDescription()+"¥"+person.cost());////Shopping List: a greent hat black shoes¥170.0
    }
}

以上代碼Person類作爲基類,Man類作爲裝飾者,HatDecorator和ShoesDecorator作爲抽象裝飾者,延伸出具體裝飾者。使得Person類實例的裝飾可以添加,而且不需要生成大量的類的個數。


九、組合模式

將對象組合成樹形結構以表示“部分整體”的層次結構。組合模式使得用戶對單個對象和使用具有一致性。

代碼例子,存在一些Composite存儲Leaf,Leaf則不能再被存儲:

package 組合模式;

import java.util.LinkedList;

abstract class Component {
    public void display(){
        System.out.println("Not able to display");
    }
    public void remove(){
        System.out.println("Not able to remove");
    }
}
class Leaf extends Component {
    private String name;
    public Leaf(String name){
        this.name = name;
    }
    @Override
    public void display(){
        System.out.print(name+" ");
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Leaf other = (Leaf) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}
class Composite extends Component {
    private LinkedList<Leaf> leafList;
    public Composite(LinkedList<Leaf> leafList){
        this.leafList = leafList;
    }
    @Override
    public void display(){
        for(int i = 0; i<leafList.size(); i++){
            leafList.get(i).display();
        }
        System.out.println();
    }
    @Override
    public void remove(){
        leafList.clear();
        leafList = null;
    }
    public boolean remove(Leaf leaf){
        return leafList.remove(leaf);
    }
}
public class Main {
    public static void main(String[] args) {
        LinkedList<Leaf> leafList = new LinkedList<Leaf>();
        leafList.add(new Leaf("leaf1"));
        leafList.add(new Leaf("leaf2"));
        leafList.add(new Leaf("leaf3"));
        Composite composite = new Composite(leafList);
        composite.display();//leaf1 leaf2 leaf3 
        composite.remove(new Leaf("leaf2"));
        composite.display();//leaf1 leaf2
    }
}

當發現需求中是體現部分與整體層次結構時,以及你希望用戶可以忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象時,就應該考慮組合模式了。


十、外觀(門面)模式

爲子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。簡單來說就是把子系統中常用的模塊放到門面類(可以有多個)中,客戶端訪問子系統直接訪問門面類就好,相當於一個小區的門衛。

代碼例子,我們有三個模塊,使用一個門面類包裝這三個模塊:

package 外觀模式;

class ModelA {
    public void methodA(){
        System.out.println("This is methodA");
    }
}
class ModelB {
    public void methodB(){
        System.out.println("This is methodB");
    }
}
class ModelC {
    public void methodC(){
        System.out.println("This is methodC");
    }
}
class Facade {
    private ModelA a = new ModelA();
    private ModelB b = new ModelB();
    private ModelC c = new ModelC();
    public void MethodA(){
        a.methodA();
    }
    public void MethodB(){
        b.methodB();
    }
    public void MethodC(){
        c.methodC();
    }
}
public class Main {
    public static void main(String[] args) {
        Facade facade = new Facade();//獲取門面對象
        facade.MethodA();//使用門面對象
        facade.MethodB();
        facade.MethodC();
    }
}
適用環境:

在開發階段,子系統往往因爲不斷的重構演化而變得越來越複雜,增加外觀Facade可以提供一個簡單的接口,減少它們之間的依賴。

在維護一個遺留的大型系統時,可能這個系統已經非常難以維護和擴展了,可以爲新系統開發一個外觀Facade類,來提供設計粗糙或高度複雜的遺留代碼的比較清晰簡單的接口,讓新系統與Facade對象交互,Facade與遺留代碼交互所有複雜的工作。

優點:
實現了子系統與客戶端之間的鬆耦合關係。

客戶端屏蔽了子系統組件,減少了客戶端所需處理的對象數目,並使得子系統使用起來更加容易。


十一、享元模式

在享元模式中可以共享的相同內容稱爲 內部狀態(Intrinsic State),而那些需要外部環境來設置的不能共享的內容稱爲 外部狀態(Extrinsic State),其中外部狀態和內部狀態是相互獨立的,外部狀態的變化不會引起內部狀態的變化。由於區分了內部狀態和外部狀態,因此可以通過設置不同的外部狀態使得相同的對象可以具有一些不同的特徵,而相同的內部狀態是可以共享的。也就是說,享元模式的本質是分離與共享 : 分離變與不變,並且共享不變。把一個對象的狀態分成內部狀態和外部狀態,內部狀態即是不變的,外部狀態是變化的;然後通過共享不變的部分,達到減少對象數量並節約內存的目的

在享元模式中通常會出現工廠模式,需要創建一個享元工廠來負責維護一個享元池(Flyweight Pool)(用於存儲具有相同內部狀態的享元對象)。在享元模式中,共享的是享元對象的內部狀態,外部狀態需要通過環境來設置。在實際使用中,能夠共享的內部狀態是有限的,因此享元對象一般都設計爲較小的對象,它所包含的內部狀態較少,這種對象也稱爲 細粒度對象。

享元模式的目的就是使用共享技術來實現大量細粒度對象的複用

代碼例子,我們使用一個字符串作爲內蘊條件,另一個字符串作爲外蘊條件,使用享元模式:

package 享元模式;

import java.util.HashMap;
import java.util.Map;

//抽象享元角色類
interface FlyWeight {
    void operation(String outerState);
}
//具體享元角色類 存在一個內蘊條件 傳入外蘊條件
class ConcreteFlyWeight implements FlyWeight {
    //內蘊狀態
    private String intrinsicState; 
    public ConcreteFlyWeight(String state) {
        this.intrinsicState = state;
    }
    @Override
    public void operation(String outerState) {
        //處理內蘊條件
        System.out.println("內蘊條件爲:"+intrinsicState);
        //處理外蘊條件
        System.out.println("外蘊條件爲:"+outerState);
    }
}
//享元工廠角色類 客戶端不可以直接將享元類實例化,而必須通過一個工廠對象獲取
class FlyWeightFactory {
    //內蘊狀態和享元對象的映射
    private Map<String,FlyWeight> cache = new HashMap<String,FlyWeight>();
    public FlyWeight getFlyWeight(String intrinsicState){
        FlyWeight flyWeight = cache.get(intrinsicState);
        if(flyWeight == null){
            //不存在則創建一個新的 然後放入緩存
            flyWeight = new ConcreteFlyWeight(intrinsicState);
            cache.put(intrinsicState, flyWeight);
        }
        return flyWeight;
    }
}
public class Main {
    public static void main(String[] args) {
        FlyWeightFactory factory = new FlyWeightFactory();
        FlyWeight flyWeight1 = factory.getFlyWeight("String1");
        factory.getFlyWeight("String2").operation("外蘊條件");//內蘊條件爲:String2 外蘊條件爲:外蘊條件
        FlyWeight flyWeight3 = factory.getFlyWeight("String1");
        System.out.println(flyWeight1==flyWeight3);//true
    }
}

可以看到相同的內蘊條件下是共享對象的,而不會新建一個對象,我們可以在內蘊條件相同的情況下加上外蘊條件,實現內蘊條件相同的對象共享,而外蘊條件自己添加。本質就是將對象分爲相同的部分和不同的部分,多個想要獲得這個對象的地方共享這個對象的相同部分,添加上這個對象的不同部分構成自己所需要的對象。實現了享元。

優點:
1、享元模式的優點在於它能夠極大的減少系統中對象的個數。
2、享元模式由於使用了外蘊狀態,外蘊狀態相對獨立,不會影響到內蘊狀態,
所以享元模式使得享元對象能夠在不同的環境被共享。
缺點
1、由於享元模式需要區分外蘊狀態和內蘊狀態,使得應用程序在某種程度上來說更加複雜化了。

2、爲了使對象可以共享,享元模式需要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變長。


代理模式參考自https://www.cnblogs.com/cenyu/p/6289209.html,做出了一些整理和刪減

十二、代理模式

爲其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用

另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。

代理模式可以分爲靜態代理、動態代理、Cglib子類代理

1、靜態代理

靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類。

代碼例子,模擬數據庫連接後的保存動作:

package 靜態代理.靜態代理;

//接口
interface UserDao {
    void save();
}
//目標對象
class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("已保存數據");
    }
}
//代理對象
class UserDaoProxy implements UserDao {
    private UserDao target;
    public UserDaoProxy(UserDaoImpl target) {
        this.target = target;
    }
    @Override
    public void save() {
        //模擬存儲之前的活動
        System.out.println("保存數據之前做的操作...");
        target.save();
        //模擬存儲之後的活動
        System.out.println("保存數據之後做的操作...");
    }
}
public class Main {
    public static void main(String[] args) {
        //目標對象
        UserDaoImpl target = new UserDaoImpl();
        //代理對象 把目標對象傳遞給代理對象 建立代理關係
        UserDaoProxy proxy = new UserDaoProxy(target);
        //執行代理方法
        proxy.save();//保存數據之前做的操作... 已保存數據 保存數據之後做的操作...
    }
}

優點:

  • 可以做到在不修改目標對象的功能前提下,對目標功能擴展。
缺點:
  • 因爲代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多。同時,一旦接口增加方法,目標對象與代理對象都要維護。

如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式。

2、動態代理

動態代理的特點:

①、代理對象不需要實現接口

②、代理對象的生成利用JDK的API,動態再內存中構建代理對象(需要我們指定創建代理對象/目標對象實現的接口)

③、動態代理也叫做JDK代理、接口代理

只需要使用Proxy類下的newProxyInstance方法,參數有三個,分別是ClassLoader loader,Class<?>[] interfaces,InvocationHandler h,分別代表指定類加載器、目標對象實現的接口類型、事件處理,執行目標對象的方法時,會觸發事件處理器的方法,把當前執行目標對象的方法作爲參數傳入,這個參數需要實現InvocationHandler。

代碼例子,模擬數據庫的保存:

package 代理模式.動態代理;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//接口
interface UserDao {
    void save(int num);
}
//目標對象
class UserDaoImpl implements UserDao {
    @Override
    public void save(int num){
        System.out.println("已保存數據"+num);
    }
}
//代理對象工廠 獲取代理對象 不需要實現接口 但是需要指定接口類型
class ProxyFactory {
    //一個代理對象工廠維護一個目標對象 生產代理對象
    private Object target;
    public ProxyFactory(Object target){
        this.target = target;
    }
    //給目標對象生產代理對象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //保存數據之前的操作
                System.out.println("保存數據之前做的操作...");
                Object result = method.invoke(target, args);
                System.out.println("保存數據之後做的操作...");
                return result;
            }
        });
    }
}
public class Main {
    public static void main(String[] args) {
        //目標對象
        UserDao target = new UserDaoImpl();
        //這是目標對象
        System.out.println(target.getClass());//class 代理模式.動態代理.UserDaoImpl
        //給定目標對象 創建代理對象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
        System.out.println(proxy.getClass());//class 代理模式.動態代理.$Proxy0
        //通過代理對象執行方法
        proxy.save(5);
        //保存數據之前做的操作...
        //已保存數據5
        //保存數據之後做的操作...
    }
}

代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能用動態代理

3、Cglib代理

上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做:Cglib代理。

①、Cglib需要引入Cglib的jar包,但是Spring核心包已經包含了Cglib功能,所以直接引入spring.jar即可

②、引入功能包後就可以在內存中動態構建子類。

③、代理的類不能爲final,不然會報錯。

④、目標對象的方法如果爲final/static,那麼就不會被攔截,也就是不會執行目標對象額外的業務方法。

⑤、這種代理可以允許目標對象沒有實現接口

代碼例子,還是我們的數據庫存儲例子:

package 代理模式.Cglib代理;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;


class UserDao {
    public void save(int num){
        System.out.println("已保存數據"+num);
    }
}
//Cglib子類代理工廠 對UserDao在內存中動態創建一個子類對象
class ProxyFactory implements MethodInterceptor {
    //目標對象
    private Object target;
    public ProxyFactory(Object target){
        this.target = target;
    }
    public Object getProxyInstance(){
        //1、工具類
        //Enhancer允許爲非接口類型創建一個Java代理。
        //Enhancer動態創建了給定類型的子類但是攔截了所有的方法。和Proxy不一樣的是,不管是接口還是類他都能正常工作。
        Enhancer en = new Enhancer();
        //2、設置父類
        en.setSuperclass(target.getClass());
        //3、設置回調函數
        en.setCallback(this);
        //4、創建子類(代理對象)
        return en.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("保存數據之前做的操作...");
        Object result = method.invoke(target, args);
        System.out.println("保存數據之後做的操作...");
        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        //目標對象
        UserDao target = new UserDao();
        //代理對象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
        proxy.save(4);
    }
}

這是在字節碼層面新生成的一個對象做的代理,效率自然比反射高。

如果加入容器的目標對象有實現接口,用JDK代理(動態代理)就好,如果沒有實現接口,用Cglib代理。

優點:
1、代理模式能將代理對象與真正被調用的對象分離,在一定程度上降低了系統的耦合度。
2、代理模式在客戶端和目標對象之間起到一箇中介作用,這樣可以起到保護目標對象的作用。
代理對象也可以對目標對象調用之前進行其他操作。
缺點:
1、在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢。

2、增加了系統的複雜度。

後面的第13-23種設計模式見我的另一篇文章

http://blog.csdn.net/qq_35580883/article/details/79327771

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章