設計模式學習筆記
面向對象六大設計原則
開閉原則
- Open Closed Principle
- 核心思想:對擴展開放,對修改關閉
- 也就是說,對已經使用的類的改動是通過增加代碼進行的,而不是修改現有代碼
單一職責原則
- Single Responsibility Principle
- 高內聚、低耦合
- 每個類只有一個職責,對外只提供一種功能,而引起變化的原因應只有一個
- 在設計模式中, 所有的設計模式都遵循這一原則
裏式替換原則
- Liskov Substitution Principle
- 核心思想:在任何父類出現的地方都可以用它的子類來替換(多態)
- 也就是說,同一個繼承體系中的對象應該有共同的行爲特徵
- 總結:只要是子類中想被其它類調用的方法,都需要在父類或者接口中出現
依賴倒轉原則
- Dependency Inversion Principle
- 核心思想:要依賴抽象和接口,不要依賴於具體實現
- 其實就是說:在應用程序中,所有的類如果使用或依賴於其他的類,則應該依賴這些其他類的抽象類或者接口,而不是直接依賴這些其他類的具體類。
- 爲了實現這一原則,就要求我們在編程的時候針對抽象類或者接口編程,而不是針對具體實現編程。
接口分離原則
- Interface Segregation Principle
- 核心思想:不應該強迫程序依賴它們不需要使用的方法
- 其實就是說:一個接口不需要提供太多行爲,一個接口應該只提供一種對外的功能,不應該把所有操作都封裝到一個接口中
迪米特原則
- Principle of Least Knowledge,也叫:最少認知原則
- 核心思想:一個對象應當對其他對象儘可能少的瞭解
- 其實就是說:降低各個對象之間的耦合,提高系統的可維護性。在模塊之間應該只通過接口編程,而不理會模塊的內部工作原理,它可以使各個模塊耦合度降到最低,促進軟件的複用
認識設計模式
設計模式概述
- 設計模式(Design pattern)代表了最佳的實踐,通常被有經驗的面向對象的軟件開發人員所採用。
- 設計模式是軟件開發人員在軟件開發過程中面臨的一般問題的解決方案,這些方案是衆多軟件開發人員經過相當長的一段時間的試驗和錯誤總結出來的。
- 設計模式是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
- 設計模式不是一種方法和技術,而是一種思想。
- 設計模式和具體的語言無關,學習設計模式就是要建立面向對象的思想,儘可能的面向接口編程,低耦合,高內聚,使設計的程序可複用。
- 學習設計模式能夠促進對面向對象思想的理解,反之亦然。它們相輔相成。
設計模式的類型
總體來說,設計模式分爲三類23種:
- 創建型(5種):工廠模式(簡單工廠、工廠方法)、抽象工廠模式、單例模式、原型模式、構建者模式
- 結構型(7種):適配器模式、裝飾模式、代理模式、外觀模式、橋接模式、組合模式、享元模式
- 行爲型(11種):模板方法模式、策略模式、觀察者模式、中介者模式、狀態模式、責任鏈模式、命令模式、迭代器模式、訪問者模式、解釋器模式、備忘錄模式
JavaEE設計模式:MVC、委託模式
設計模式
創建型
工廠模式
簡單工廠模式
- 簡單工廠模式包含3個角色:SimpleFactory(簡單工廠)、AbstractProduct(抽象產品)和ConcreteProduct(具體產品),工廠擁有一個工廠方法(create),接收一個參數,通過不同的參數實例化不同的產品類。
- 圖示
- 優點:很明顯,“簡單粗暴”,通過一個含參的工廠方法,可以實例化任何產品,上至飛機火箭、下至土豆麪條,無所不能。簡單工廠又名:上帝類,上帝工廠。
- 缺點:任何東西的子類都可以被生產,負擔太重。當需要生產的產品種類非常多時,工廠方法的代碼量可能會很龐大。在遵循開閉原則的條件下,簡單工廠對於增加新產品,無能爲力,因爲增加新產品只能通過修改工廠方法來實現。
- 代碼示例:
/**
* 抽象產品類
*/
public abstract class Product {
// 所有產品類的公共業務方法
public void methodSame(){
// 公共方法具體實現
}
// 聲明抽象業務方法
public abstract void methodDiff();
}
/**
* 具體產品A
*/
public class ConcreteProductA extends Product {
@Override
public void methodDiff() {
System.out.println("ConcreteProductA");
}
}
/**
* 具體產品B
*/
public class ConcreteProductB extends Product {
@Override
public void methodDiff() {
System.out.println("ConcreteProductB");
}
}
/**
* 產品類型
*/
public class ProductTypes {
final static String TYPE_A = "A";
final static String TYPE_B = "B";
}
/**
* 普通-簡單工廠
*/
public class PlainSimpleFactory {
public static Product create(String type){
Product product = null;
if(type.equalsIgnoreCase(ProductTypes.TYPE_A)){
product = new ConcreteProductA();
}else if(type.equalsIgnoreCase(ProductTypes.TYPE_B)){
product = new ConcreteProductB();
}
return product;
}
}
/**
* 靜態方法-簡單工廠
*/
public class StaticSimpleFactory {
public static ConcreteProductA createA(){
return new ConcreteProductA();
}
public static ConcreteProductB createB(){
return new ConcreteProductB();
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Product productA1 = PlainSimpleFactory.create(ProductTypes.TYPE_A);
productA1.methodDiff();
Product productB1 = PlainSimpleFactory.create(ProductTypes.TYPE_B);
productB1.methodDiff();
Product productA2 = StaticSimpleFactory.createA();
productA2.methodDiff();
Product productB2 = StaticSimpleFactory.createB();
productB2.methodDiff();
}
}
工廠方法模式
- 工廠方法是針對每一種產品提供一個工廠類,通過不同的工廠實例來創建不同的產品實例
- 工廠方法模式包含4個角色:AbstractProduct(抽象產品)、ConcreteProduct(具體產品)、FactoryMethod(工廠方法接口)和ConcreteFactory(具體工廠)
- 圖示
- 優點:很好地減輕了工廠類的負擔,把某一類/某一種東西交由一個工廠生產;同時增加某一類東西並不需要修改工廠類,只需要添加生產這類東西的工廠即可,使得工廠類符合開閉原則
- 缺點:對於某些可以形成產品族的情況處理比較複雜
- 示例代碼:
/**
* 方法工廠接口
*/
public interface MethodFactory {
public Product create();
}
/**
* 具體的方法工廠A
*/
public class ConcreteFactoryA implements MethodFactory {
@Override
public Product create() {
return new ConcreteProductA();
}
}
/**
* 具體的方法工廠B
*/
public class ConcreteFactoryB implements MethodFactory {
@Override
public Product create() {
return new ConcreteProductB();
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
MethodFactory factoryA = new ConcreteFactoryA();
Product productA = factoryA.create();
productA.methodDiff();
MethodFactory factoryB = new ConcreteFactoryB();
Product productB = factoryB.create();
productB.methodDiff();
}
}
抽象工廠模式
- 抽象工廠是應對產品族概念的,上邊的工廠方法模式是一種極端情況下的抽象工廠模式(即只生產一種產品的抽象工廠模式),而抽象工廠模式可以看成是工廠方法模式的一種推廣。
- 圖示:
- 抽象工廠模式包含4個角色:AbstractProduct(抽象產品,有多個)、ConcreteProduct(具體產品)、AbstractFactory(抽象工廠接口)和ConcreteFactory(具體工廠)
- 示例代碼
/**
* 抽象產品A
*/
public abstract class ProductA {
// 所有產品類的公共業務方法
public void methodSame(){
// 公共方法具體實現
}
// 聲明抽象業務方法
public abstract void methodDiff();
}
/**
* 抽象產品B
*/
public abstract class ProductB {
// 所有產品類的公共業務方法
public void methodSame(){
// 公共方法具體實現
}
// 聲明抽象業務方法
public abstract void methodDiff();
}
/**
* 具體產品A1
*/
public class ConcreteProductA1 extends ProductA {
@Override
public void methodDiff() {
System.out.println("ConcreteProductA1");
}
}
/**
* 具體產品A2
*/
public class ConcreteProductA2 extends ProductA {
@Override
public void methodDiff() {
System.out.println("ConcreteProductA2");
}
}
/**
* 具體產品B1
*/
public class ConcreteProductB1 extends ProductB {
@Override
public void methodDiff() {
System.out.println("ConcreteProductB1");
}
}
/**
* 具體產品B2
*/
public class ConcreteProductB2 extends ProductB {
@Override
public void methodDiff() {
System.out.println("ConcreteProductB2");
}
}
/**
* 抽象工廠
*/
public interface AbstractFactory {
ProductA createA();
ProductB createB();
}
/**
* 具體工廠1
*/
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createA() {
return new ConcreteProductA1();
}
@Override
public ProductB createB() {
return new ConcreteProductB1();
}
}
/**
* 具體工廠2
*/
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createA() {
return new ConcreteProductA2();
}
@Override
public ProductB createB() {
return new ConcreteProductB2();
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA = factory1.createA();
productA.methodDiff();
ProductB productB = factory1.createB();
productB.methodDiff();
AbstractFactory factory2 = new ConcreteFactory2();
productA = factory2.createA();
productA.methodDiff();
productB = factory2.createB();
productB.methodDiff();
}
}
構建者模式
- 將一個複雜的對象的構造與它的表示分離,使同樣的構造過程可以創建不同的表示。
- 構建者模式包含4個角色:AbstractBuilder(抽象構建者)、ConcreteBuilder(具體構建者)、Product(產品)和Director(指揮者)
- 圖示:
- 示例代碼
/**
* 產品類
*/
public class Product {
// 定義產品的各個部件
private String partA;
private String partB;
private String partC;
public String getPartA() {
return partA;
}
public void setPartA(String partA) {
this.partA = partA;
}
public String getPartB() {
return partB;
}
public void setPartB(String partB) {
this.partB = partB;
}
public String getPartC() {
return partC;
}
public void setPartC(String partC) {
this.partC = partC;
}
@Override
public String toString() {
return "Product [partA=" + partA + ", partB=" + partB + ", partC=" + partC + "]";
}
}
/**
* 構建者接口
*/
public interface Builder {
Builder buildPartA();
Builder buildPartB();
Builder buildPartC();
Product build();
}
/**
* 具體構建者
*/
public class ConcreteBuilder implements Builder {
private Product product = new Product();
@Override
public Builder buildPartA() {
product.setPartA("A");
return this;
}
@Override
public Builder buildPartB() {
product.setPartB("B");
return this;
}
@Override
public Builder buildPartC() {
product.setPartC("C");
return this;
}
@Override
public Product build() {
return product;
}
}
/**
* 導演類
*/
public class Director {
private Builder builder;
public Director(Builder builder){
this.builder = builder;
}
// 產品構建與組裝方法
public Product constructProduct(){
return builder.buildPartA().buildPartB().buildPartC().build();
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.constructProduct();
System.out.println(product); // Product [partA=A, partB=B, partC=C]
}
}
使用構建者模式的好處
- 使用構建者模式可以使客戶端不必知道產品內部組成的細節
- 具體的構建者類之間是相互獨立的,這有利於系統的擴展
- 具體的構建者相互獨立,因此可以對建造的過程逐步細化,而不會對其他模塊產生任何影響
使用場景
- 創建一些複雜對象時,這些對象的內部組成部分之間的建造順序是穩定的,但對象的內部組成構建面臨着複雜的變化
- 要創建的複雜對象的算法,獨立於該對象的組成部分,也獨立於組成部分的裝配方法時
單例模式
- 介紹:單例對象(Singleton)是一種常用的設計模式,在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例。好處是:
1)某些類創建比較頻繁,對於一些大型的對象,這是一筆很大的系統開銷
2)省去了new操作符,降低了系統內存的使用頻率,減輕GC壓力 - 圖示
餓漢式
- 在調用獲取實例方法(getInstance)之前就把對象創建好了
- 實現:
public class HungrySingleton {
// 1、構造私有
private HungrySingleton() {
}
// 2、成員變量初始化本身對象,線程安全的
private static HungrySingleton singleton = new HungrySingleton();
// 3、對外提供公共方法獲取對象
public static HungrySingleton getInstance() {
return singleton;
}
}
- 測試:
public class SingletonTest {
public static void main(String[] args) {
// 餓漢式
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
if (instance1 == instance2) {
System.out.println("兩個對象是相同實例");
} else {
System.out.println("兩個對象是不同實例");
}
}
}
懶漢式
- 在調用獲取實例方法的時候纔去創建對象 – 懶加載、延遲加載
- 步驟
* 構造私有
* 定義私有靜態成員變量,先不初始化
* 定義公開靜態方法,獲取本身對象
* 有對象就返回已有對象
* 沒有對象,再去創建
- 實現:
public class LazySingleton1 {
private LazySingleton1() {}
private static LazySingleton1 instance = null;
public static LazySingleton1 getInstance() {
if (instance == null) {
instance = new LazySingleton1();
}
return instance;
}
}
- 線程安全問題,判斷依據:
* 是否存在多線程
* 是否有共享數據
* 否存在非原子性操作
問題:這個類可以滿足基本要求,但是,懶漢式單例實現,存在線程安全隱患,如何解決?
我們首先會想到對getSingletonInstance方法加synchronized關鍵字,LazySingleton2
/**
* synchronized關鍵字鎖住的是這個對象,這樣的用法,在性能上會有所下降
* 因爲每次調用getInstance(),都要對對象上鎖
* 事實上,只有在第一次創建對象的時候需要加鎖,之後就不需要了
* 所以,這個地方需要改進
*/
public class LazySingleton2 {
private LazySingleton2() {}
private static LazySingleton2 instance = null;
public static synchronized LazySingleton2 getInstance() {
if (instance == null) {
instance = new LazySingleton2();
}
return instance;
}
}
- 雙重檢測鎖方式實現:
/**
* 似乎解決了之前提到的問題,將synchronized關鍵字加在了內部,也就是說當調用的時候是不需要加鎖的,只有在instance爲null,並創建對象的時候才需要加鎖,性能有一定的提升
* 但是,這樣的情況,還是有可能有問題的,看下面的情況:
* 在Java指令中創建對象和賦值操作是分開進行的,也就是說instance = new Singleton();語句是分兩步執行的。
* 但是JVM並不保證這兩個操作的先後順序,也就是說有可能JVM會爲新的Singleton實例分配空間,然後直接賦值給instance成員,然後再去初始化這個Singleton實例。
* 這樣就可能出錯了,我們以A、B兩個線程爲例:
* a>A、B線程同時進入了第一個if判斷
*
* b>A首先進入synchronized塊,由於instance爲null,所以它執行instance = new Singleton();
*
* c>由於JVM內部的優化機制,JVM先畫出了一些分配給Singleton實例的空白內存,並賦值給instance成員(注意此時JVM沒有開始初始化這個實例),然後A離開了synchronized塊。
*
* d>B進入synchronized塊,由於instance此時不是null,因此它馬上離開了synchronized塊並將結果返回給調用該方法的程序。
*
* e>此時B線程打算使用Singleton實例,卻發現它沒有被初始化,於是錯誤發生了。
*
* 所以程序還是有可能發生錯誤,其實程序在運行過程是很複雜的,從這點我們就可以看出,尤其是在寫多線程環境下的程序更有難度,有挑戰性。
* 我們對該程序還得做進一步優化
*/
public class LazySingleton3 {
private LazySingleton3() {}
private static LazySingleton3 instance = null;
public static LazySingleton3 getInstance() {
if (instance == null) {
synchronized (LazySingleton3.class) {
if (instance == null) {
instance = new LazySingleton3();
// 開闢JVM堆空間 -> 產生堆內存地址保存到棧內存的Student引用中 -> 創建對象
// 存在的問題:這裏是非原子性的
// 當“內存地址保存到棧內存的Student引用中”,又有一個線程進來,這個時候student != null,該線程拿到一個還沒有創建的對象,會出現空指針異常
}
}
}
return instance;
}
}
- 靜態內部類實現:
public class LazySingleton4 {
private LazySingleton4() {
}
/*
* 此處使用一個內部類來維護單例 JVM在類加載的時候,是互斥的,所以可以由此保證線程安全問題
*/
private static class SingletonFactory {
private static LazySingleton4 instance = new LazySingleton4();
}
public static LazySingleton4 getInstance() {
return SingletonFactory.instance;
}
}
- 測試:
public class SingletonTest {
public static void main(String[] args) {
LazySingleton4 instance3 = LazySingleton4.getInstance();
LazySingleton4 instance4 = LazySingleton4.getInstance();
if (instance3 == instance4) {
System.out.println("兩個對象是相同實例"); // ^_^
} else {
System.out.println("兩個對象是不同實例");
}
}
}
枚舉創建
- 我們還可以使用內部靜態枚舉創建單例
- 實現:
public class EnumSingleton {
// 私有化構造函數
private EnumSingleton() {
}
static enum SingletonEnum {
//創建一個枚舉對象,該對象天生爲單例
INSTANCE;
private EnumSingleton singleton;
// 私有化枚舉的構造函數
private SingletonEnum() {
singleton = new EnumSingleton();
}
public EnumSingleton getInstance() {
return singleton;
}
}
// 對外暴露一個獲取User對象的靜態方法
public static EnumSingleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
}
原型模式
- 介紹:原型模式雖然是創建型的模式,但是與工廠模式沒有關係。從名字即可看出,該模式的思想就是將一個對象作爲原型,對其進行復制、克隆,產生一個和原對象類似的新對象。
實現Cloneable接口
/**
* 原型類
*/
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype prototype = (Prototype) super.clone();
return prototype;
}
}
- 很簡單,一個原型類,只需要實現Cloneable接口,覆寫clone方法。此處的clone方法可以改成任意名稱,因爲Cloneable接口是一個空接口,我們可以任意自定義實現類的方法,重點在於
super.clone()
,super.clone()
調用的是Object的clone()方法,而在Object類中clone()是native的。
通用實現
- 在具體原型類的克隆方法中實例化一個與自身類型相同的對象並將其返回,同時將相關的參數傳入新創建的對象中,保證它們的成員變量相同。
- 圖示:
- 示例代碼
/**
* 原型接口
*/
public interface Prototype {
Prototype clone();
}
/**
* 具體原型實現
*/
public class ConcretePrototype implements Prototype {
private String attr;
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
@Override
public String toString() {
return "ConcretePrototype [attr=" + attr + "]";
}
@Override
public Prototype clone() {
// 創建新對象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAttr(attr);
return prototype;
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
ConcretePrototype prototype1 = new ConcretePrototype();
prototype1.setAttr("123");
ConcretePrototype prototype2 = (ConcretePrototype) prototype1.clone();
System.out.println(prototype2);
}
}
- 淺複製:將一個對象複製後,基本數據類型都會被重新創建,但是引用類型指向的還是原對象所指向的
- 深複製:將一個對象複製後,不論是基本數據類型還是引用類型,都會被重新創建。
/**
* 深淺複製
*/
public class DeepShallowCopy implements Cloneable, Serializable{
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
/**
* 淺複製
*/
public Object shallowClone() throws CloneNotSupportedException{
DeepShallowCopy proto = (DeepShallowCopy) super.clone();
return proto;
}
/**
* 深複製
* @throws ClassNotFoundException
*/
public Object deepClone() throws IOException, ClassNotFoundException{
// 寫入當前對象的二進制流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 讀出二進制流產生的新對象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
class SerializableObject implements Serializable{
private static final long serialVersionUID = 1L;
}
- 測試:
public class Test {
public static void main(String[] args) throws Exception {
// 創建原對象
DeepShallowCopy prototype = new DeepShallowCopy();
prototype.setString("zhangsan");
prototype.setObj(new SerializableObject());
System.out.println("克隆之前的對象:" + prototype.getString());
System.out.println("克隆之前的對象:" + prototype.getObj());
// 淺複製出來的對象
DeepShallowCopy shallowClone = (DeepShallowCopy) prototype.shallowClone();
System.out.println("淺複製出來的對象:" + shallowClone.getString());
System.out.println("淺複製出來的對象:" + shallowClone.getObj());
// 深複製出來的對象
DeepShallowCopy deepClone = (DeepShallowCopy) prototype.deepClone();
System.out.println("深複製出來的對象:" + deepClone.getString());
System.out.println("深複製出來的對象:" + deepClone.getObj());
}
}
應用
- spring framework中BeanUtils工具類的copyProperties方法:Springframework的BeanUtils.copyProperties原理源碼淺層解析
結構型
結構型模式有7種:適配器模式、裝飾模式、代理模式、外觀模式、橋接模式、組合模式和享元模式,其中對象的適配模式是各種模式的起源。
適配器模式
- 介紹:適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由於接口不匹配所造成的類的兼容性問題。主要分爲三類:類的適配器模式、對象的適配器模式和接口的適配器模式。
- 適配器模式包含3個角色:Target(目標抽象類)、Adapter(適配器類)和Adaptee(適配者類)
類的適配器模式
- 核心思想:有一個
Adaptee
類,擁有一個方法待適配;目標接口是Target
,通過Adapter
類,將Adaptee
類的功能擴展到Target
裏 - 圖示
- 示例代碼
/**
* 適配者類
*/
public class Adaptee {
public void method1(){
System.out.println("This is Adaptee's method");
}
}
/**
* 目標接口
*/
public interface Target {
// 與適配者類中的方法相同
void method1();
// 新類的方法
void method2();
}
/**
* 類適配器類
*/
public class ClassAdapter extends Adaptee implements Target {
@Override
public void method2() {
System.out.println("This is Target's method");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Target target = new ClassAdapter();
target.method1(); // This is Adaptee's method
target.method2(); // This is Target's method
}
}
這樣Target
接口的實現就具有了Adaptee
類的功能
對象的適配器模式
- 基本思路和類的適配器模式相同,只是將
Adapter
類作了修改,方式是:不繼承Adaptee
,而是持有Apaptee
類的實例,以達到解決兼容性的問題。 - 圖示:
- 示例代碼:修改適配器類即可
/**
* 對象適配器類
*/
public class ObjectAdapter implements Target{
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee){
super();
this.adaptee = adaptee;
}
@Override
public void method1() {
adaptee.method1();
}
@Override
public void method2() {
System.out.println("This is Target's method");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.method1(); // This is Adaptee's method
target.method2(); // This is Target's method
}
}
接口的適配器模式
- 有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現類時,必須實現該接口的所有方法,這明顯有時比較浪費,因爲並不是所有的方法都是我們需要的,有時只需要某一些。爲了解決此問題,引入接口適配模式。藉助於一個抽象類,該抽象類實現了該接口實現了所有接口,而我們不和原始接口打交道,只和該抽象類取得聯繫,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行
- 圖示
- 示例代碼
/**
* 接口適配抽象類
*/
public abstract class InterfaceAdapter implements Target{
public void method1(){
System.out.println("InterfaceAdapter has implement Target's method1");
}
public void method2(){
System.out.println("InterfaceAdapter has implement Target's method2");
}
}
/**
* 目標類A
*/
public class TargetA extends InterfaceAdapter {
public void method1(){
System.out.println("This is TargetA's method1");
}
}
/**
* 目標類B
*/
public class TargetB extends InterfaceAdapter {
public void method2(){
System.out.println("This is TargetB's method2");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Target targetA = new TargetA();
Target targetB = new TargetB();
targetA.method1();
targetA.method2();
targetB.method1();
targetB.method2();
// 輸出結果:
// This is TargetA's method1
// InterfaceAdapter has implement Target's method2
// InterfaceAdapter has implement Target's method1
// This is TargetB's method2
}
}
小結一下三種適配器模式的應用場景
- 類的適配模式:當希望將一個類轉換成滿足另一個新接口的類時,可以使用類的適配器模式,創建一個新類,繼承原來的類,並實現新的接口即可。
- 對象的適配模式:當希望將一個對象轉換成滿足另一個新接口的對象時,可以創建一個類持有原對象的一個實例,並在該類的方法中調用實例方法即可。
- 接口的適配模式:當不希望實現一個接口的所有方法時,可以創建一個抽象類實現所有的方法,我們寫別的類時,只需繼承該抽象類即可。
裝飾模式
- 介紹:裝飾模式就是給一個對象增加一些新的特性,而且是動態的,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例。
- 裝飾模式包含4個角色:Component(抽象構件)、ConcreteComponent(具體構件)、Decorator(抽象裝飾類)和ConcreteDecorator(具體裝飾類)
- 圖示:
- 示例代碼:
/**
* 抽象構件
*/
public abstract class Component {
public abstract void operation();
}
/**
* 具體構件類
*/
public class ConcreteComponent extends Component {
@Override
public void operation() {
// 構件的基本功能
System.out.println("ConcreteComponent's basic function");
}
}
/**
* 抽象裝飾類
*/
public class Decorator extends Component {
// 持有一個抽象構件對象的引用
private Component component;
// 構造函數注入一個抽象構件類型的對象
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation(); // 調用原有的業務方法
}
}
/**
* 具體的裝飾類
*/
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation(){
beforeOperation();
super.operation(); // 調用原有業務方法
afterOperation();
}
// 新增業務方法
public void beforeOperation(){
System.out.println("before original operation");
}
public void afterOperation(){
System.out.println("after original operation");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component = new ConcreteDecorator(component);
component.operation();
// 輸出結果:
// before original operation
// ConcreteComponent's basic function
// after original operation
}
}
代理模式
- 介紹:在代理模式中引入了一樣代理對象,替代原對象進行一些操作,起到中介的作用。
- 代理模式包含3個角色:Subject(抽象主題角色)、Proxy(代理主題角色)和RealSubject(真實主題角色)
- 圖示:
靜態代理
- 示例代碼:
/**
* 抽象主題
*/
public abstract class Subject {
public abstract void request();
}
/**
* 真實主題
*/
public class RealSubject extends Subject {
@Override
public void request() {
System.out.println("RealSubject request ...");
}
}
/**
* 代理類
*/
public class Proxy extends Subject {
private RealSubject realSubject = new RealSubject();
@Override
public void request() {
preRequest();
realSubject.request();
postRequest();
}
private void preRequest(){
System.out.println("before request ...");
}
private void postRequest(){
System.out.println("after request ...");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request();
}
// 結果輸出
// before request ...
// RealSubject request ...
// after request ...
}
動態代理
- JDK動態代理
public class JDKProxyFactory implements InvocationHandler {
// 目標對象的引用
private Object target;
// 通過構造函數將目標對象注入到代理對象中
public JDKProxyFactory(Object target) {
super();
this.target = target;
}
public Object getProxy(){
// 如何生成一個代理類呢?
// 1、編寫源文件(java文件)----目錄類接口interface實現類(調用了目標對象的方法)
// 2、編譯源文件爲class文件
// 3、將class文件加載到JVM中(ClassLoader)
// 4、將class文件對應的對象進行實例化(反射)
// Proxy是JDK中的API類
// 第一個參數:目標對象的類加載器
// 第二個參數:目標對象的接口
// 第三個參數:代理對象的執行處理器
Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
return object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method method2 = target.getClass().getMethod("saveUser", null);
Method method3 = Class.forName("com.sun.proxy.$Proxy2").getMethod("saveUser", null);
System.out.println("目標對象的方法:" + method2.toString());
System.out.println("目標接口的方法:" + method.toString());
System.out.println("代理對象的方法:" + method3.toString());
System.out.println("這是jdk的代理方法");
// 下面的代碼,是反射中的API用法
// 該行代碼,實際調用的是[目標對象]的方法
// 利用反射,調用[目標對象]的方法
Object returnValue = method.invoke(target, args);
return returnValue;
}
}
- CGLIB動態代理
public class CgLibProxyFactory implements MethodInterceptor {
public Object getProxyByCgLib(Class clazz) {
// 創建增強器
Enhancer enhancer = new Enhancer();
// 設置需要增強的類的類對象
enhancer.setSuperclass(clazz);
// 設置回調函數
enhancer.setCallback(this);
// 獲取增強之後的代理對象
return enhancer.create();
}
/***
* Object proxy:這是代理對象,也就是[目標對象]的子類
* Method method:[目標對象]的方法
* Object[] arg:參數
* MethodProxy methodProxy:代理對象的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
// 因爲代理對象是目標對象的子類
// 該行代碼,實際調用的是父類目標對象的方法
System.out.println("這是cglib的代理方法");
// 通過調用子類[代理類]的invokeSuper方法,去實際調用[目標對象]的方法
Object returnValue = methodProxy.invokeSuper(proxy, arg);
// 代理對象調用代理對象的invokeSuper方法,而invokeSuper方法會去調用目標類的invoke方法完成目標對象的調用
return returnValue;
}
}
- 測試代碼
public class TestProxy {
@Test
public void testJDKProxy() {
// 1、創建目標對象
UserService service = new UserServiceImpl();
// 2、生成代理對象
JDKProxyFactory proxyFactory = new JDKProxyFactory(service);
// 得到代理對象
UserService proxy = (UserService) proxyFactory.getProxy();
// 生成class文件
generatorClass(proxy);
// 3、調用目標對象的方法
service.saveUser();
System.out.println("===============");
// 4、調用代理對象的方法
proxy.saveUser();
}
@Test
public void testCgLibProxy() {
// 創建目標對象
UserService service = new UserServiceImpl();
// 生成代理對象
CgLibProxyFactory proxyFactory = new CgLibProxyFactory();
UserService proxy = (UserService) proxyFactory.getProxyByCgLib(service.getClass());
// 調用目標對象的方法
service.saveUser();
System.out.println("===============");
// 調用代理對象的方法
proxy.saveUser();
}
private void generatorClass(Object proxy) {
FileOutputStream out = null;
try {
// byte[] generateProxyClass =
// ProxyGenerator.generateProxyClass(proxy.getClass().getName(), new Class[]
// {proxy.getClass()});
byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(),
new Class[] { proxy.getClass() });
out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
out.write(generateProxyClass);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
}
外觀模式
- 介紹:爲子系統中的一組接口提供一個統一的入口。外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。外觀模式又稱門面模式,是迪米特法則的一種具體實現,通過引入一個新的外觀角色可以降低原有系統的複雜度,同時降低客戶端與子系統的耦合度。
- 外觀模式包含兩個角色:
(1)Facade(外觀角色):在客戶端可以調用它的方法,在外觀角色中可以知道相關的(一個或多個)子系統的功能和責任;在正常情況下,它將所有從客戶端發來的請求委派到相應的子系統,傳遞給相應的子系統對象處理
(2)SubSystem(子系統角色):在軟件系統中可以有一個或多個子系統角色,每一個系統可以不是一個單獨的類,而是一個類的集合,它實現了子系統的功能;每一個子系統都可以被客戶端直接調用,或者被外觀角色調用,它處理外觀類傳來的請求;子系統並不知道外觀的存在,對於子系統而言,外觀角色僅僅是另一個客戶端而已。 - 圖示:
- 示例代碼:
/**
* 子系統A
*/
public class SubSystemA {
public void methodA(){
System.out.println("SubSystemA methodA");
}
}
/**
* 子系統B
*/
public class SubSystemB {
public void methodB(){
System.out.println("SubSystemB methodB");
}
}
/**
* 子系統C
*/
public class SubSystemC {
public void methodC(){
System.out.println("SubSystemC methodC");
}
}
/**
* 外觀類
*/
public class Facade {
private SubSystemA subSystemA = new SubSystemA();
private SubSystemB subSystemB = new SubSystemB();
private SubSystemC subSystemC = new SubSystemC();
public void method(){
subSystemA.methodA();
subSystemB.methodB();
subSystemC.methodC();
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Facade facade = new Facade();
facade.method();
}
}
橋接模式
- 介紹:將抽象部分與它的實現部分解耦,使得兩者都能夠獨立變化。橋接模式用一種巧妙的方式處理多層繼承存在的問題,用抽象關聯取代了傳統的多層繼承,將類之間的靜態繼承關係轉換爲動態的對象組合關係,是的系統更加靈活,並易於擴展,同時有效地控制了系統中類的個數。
- 橋接模式包含4個角色:
(1)Abstraction(抽象類):用於定義抽象類的接口,通常是抽象類而不是接口,其中定義了一個Implementor(實現類接口)類型的對象並可以維護該對象,它與Implementor之間具有關聯關係,它既可以包含抽象業務方法,也可以包含具體業務方法。
(2)RefinedAbstraction(擴充抽象類):它擴充Abstraction定義的接口,通常情況下它不在是抽象類而是具體類,實現了在Abstraction中聲明的抽象業務方法,在RefinedAbstraction中可以調用在Implementor中定義的業務方法。
(3)Implementor(實現類接口):定義實現類的接口,該接口不一定要與Abstraction的接口完全一致,事實上這兩個接口可以完全不同。一般來講,Implementor接口僅提供基本操作,而Abstraction則定義了基於這些基本操作的較高層次的操作
(4)ConcreteImplementor(具體實現類):實現implementor接口並定義它的具體實現。 - 圖示:
- 示例代碼:
/**
* 抽象類
*/
public abstract class Abstraction {
// 定義實現類接口對象
protected Implementor impl;
public void setImpl(Implementor impl) {
this.impl = impl;
}
// 聲明抽象業務方法
public abstract void operation();
}
/**
* 擴充抽象類
*/
public class RefinedAbstraction extends Abstraction {
@Override
public void operation() {
// 調用實現類的方法
impl.operationImpl();
}
}
/**
* 實現類接口
*/
public interface Implementor {
public void operationImpl();
}
/**
* 具體實現類A
*/
public class ConcreteImplementorA implements Implementor{
@Override
public void operationImpl() {
System.out.println("ConcreteImplementorA operationImpl");
}
}
/**
* 具體實現類B
*/
public class ConcreteImplementorB implements Implementor{
@Override
public void operationImpl() {
System.out.println("ConcreteImplementorB operationImpl");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Abstraction absA = new RefinedAbstraction();
absA.setImpl(new ConcreteImplementorA());
absA.operation();
Abstraction absB = new RefinedAbstraction();
absB.setImpl(new ConcreteImplementorB());
absB.operation();
}
}
- 案例
- 代碼:
/**
* 抽象類——人
*/
public abstract class Person {
private Clothing clothing;
private String type;
public Clothing getClothing() {
return clothing;
}
public void setClothing() {
this.clothing = ClothingFactory.getClothing();
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public abstract void dress();
}
/**
* 擴充抽象類——男人
*/
public class Man extends Person {
public Man() {
setType("男人");
}
@Override
public void dress() {
Clothing clothing = getClothing();
clothing.personDressCloth(this);
}
}
/**
* 擴充抽象類——女人
*/
public class Lady extends Person {
public Lady() {
setType("女人");
}
@Override
public void dress() {
Clothing clothing = getClothing();
clothing.personDressCloth(this);
}
}
/**
* 實現類接口(抽象類)——衣服
*/
public abstract class Clothing {
public abstract void personDressCloth(Person person);
}
/**
* 具體實現類-馬甲
*/
public class Jacket extends Clothing {
@Override
public void personDressCloth(Person person) {
System.out.println(person.getType() + "穿馬甲");
}
}
/**
* 具體實現類——褲子
*/
public class Trouser extends Clothing {
@Override
public void personDressCloth(Person person) {
System.out.println(person.getType() + "穿褲子");
}
}
/**
* 衣服工廠
*/
public class ClothingFactory {
private static Clothing clothing;
public ClothingFactory(Clothing clothing) {
ClothingFactory.clothing = clothing;
}
public static Clothing getClothing(){
return clothing;
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
Person man = new Man();
Person lady = new Lady();
Clothing jacket = new Jacket();
Clothing trouser = new Trouser();
jacket.personDressCloth(man);
trouser.personDressCloth(man);
jacket.personDressCloth(lady);
trouser.personDressCloth(lady);
}
}
組合模式
-
介紹:
-
圖示:
-
示例代碼:
享元模式
-
介紹:
-
圖示:
-
示例代碼:
行爲型
模板方法模式
- 介紹:模板方法是結構最簡單的行爲型設計模式,在其結構中只存在父類和子類之間的繼承關係。模板方法模式是一種基於繼承的代碼複用技術,它是一種類行爲型模式。
- 模板方法模式包含2個角色:
(1)AbstractClass(抽象類):抽象類中定義一系列的基本操作,這些基本操作可以是具體的,也可以是抽象的,每一個基本操作對應算法的一個步驟,在其子類中可以重定義實現這些步驟。同時在抽象類中實現了一個模板方法,用於定義一個算法的框架,模板方法不僅可以調用在抽象類中實現的基本方法,也可以調用在抽象類的子類中實現的基本方法,還可以調用其他對象中的方法。
(2)ConcreteClass(具體子類):它是抽象類的子類,用於實現在父類中聲明的抽象基本操作以完成子類特定算法的步驟,也可以覆蓋在父類中已經實現的具體基本操作。 - 圖示:
- 示例代碼:
/**
* 抽象類
*/
public abstract class AbstractClass {
// 模板方法
public void templateMethod(){
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
// 基本方法——具體方法
public void primitiveOperation1(){
System.out.println("AbstractClass primitiveOperation1");
}
// 基本方法——抽象方法
public abstract void primitiveOperation2();
// 基本方法——鉤子方法
public void primitiveOperation3(){
System.out.println("AbstractClass primitiveOperation3");
}
}
/**
* 具體子類
*/
public class ConcreteClass extends AbstractClass {
@Override
public void primitiveOperation2() {
System.out.println("ConcreteClass implements primitiveOperation2");
}
public void primitiveOperation3() {
System.out.println("ConcreteClass primitiveOperation3");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
AbstractClass temp = new ConcreteClass();
temp.templateMethod();
}
// 結果輸出
// AbstractClass primitiveOperation1
// ConcreteClass implements primitiveOperation2
// ConcreteClass primitiveOperation3
}
- 案例:獲取計算時間模板
public abstract class GetTimeTemplate {
// 固定流程方法
public long getTime() {
// 獲取起始時間
long t1 = System.currentTimeMillis();
// 模板方法
code();
// 獲取結束時間
long t2 = System.currentTimeMillis();
return t2 - t1;
}
// 鉤子方法
public abstract void code();
}
/**
* 複製文件方法
*/
public class CopyFileDemo extends GetTimeTemplate {
@Override
public void code() {
try {
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("張三.jpg"));
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("mn.jpg"));
byte[] bs = new byte[256];
int len = 0;
while((len = inputStream.read(bs)) != -1){
outputStream.write(bs, 0, len);
outputStream.flush();
}
//釋放資源
inputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* for循環計算
*/
public class ForDemo extends GetTimeTemplate{
@Override
public void code() {
//輸出for循環
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
/**
* 測試
*/
public class TemplateTest {
public static void main(String[] args) {
/*GetTime time = new GetTime();
System.out.println("耗時 "+time.getTime()+" 毫秒");*/
GetTimeTemplate time = new ForDemo();
System.out.println("耗時 "+time.getTime()+" 毫秒");
GetTimeTemplate time2 = new CopyFileDemo();
System.out.println("耗時 "+time2.getTime()+" 毫秒");
}
}
策略模式
-
介紹:
-
圖示:
-
示例代碼:
觀察者模式
-
介紹:
-
圖示:
-
示例代碼:
中介者模式
-
介紹:
-
圖示:
-
示例代碼:
狀態模式
-
介紹:
-
圖示:
-
示例代碼:
責任鏈模式
-
介紹:
-
圖示:
-
示例代碼:
命令模式
-
介紹:
-
圖示:
-
示例代碼:
迭代器模式
-
介紹:
-
圖示:
-
示例代碼:
訪問者模式
-
介紹:
-
圖示:
-
示例代碼:
解釋器模式
-
介紹:
-
圖示:
-
示例代碼:
備忘錄模式
-
介紹:
-
圖示:
-
示例代碼:
JavaEE設計模式
MVC模式
委託模式
- 委託模式或者叫委派模式,委託模式不在23種設計模式之內,但是使用這種模式的應用很多。
委託模式的表現:看似是一個對象完成的功能,但是實質上通過兩個對象去實現一個功能。比如說:項目經理和開發人員的關係,客戶只告訴項目經理說做什麼需求,但是項目經理沒有真正自己去實現該需求,是安排開發人員去實現需求。
委託模式需要的兩個對象之間沒有關係。 - 示例代碼
/**
* 受託人:開發人員
*/
public class Developer {
public void development() {
System.out.println("又來新需求了,開幹");
}
}
/**
* 委託人:項目經理
*/
public class ProjectManager {
//受託人
private Developer delegate;
public ProjectManager(Developer delegate){
this.delegate = delegate;
}
public void doSomething() {
//根據需求,將實際工作派發給開發人員或者其他人員
delegate.development();
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
// 成績是委託人的,幹活的是受託人的
// 對於客戶端來說,是感受不到受託人的
ProjectManager pm = new ProjectManager(new Developer());
pm.doSomething();
}
}