技術博客:www.zhenganwen.topphp
暨上文介紹了
PlantUML
以後,謹以此文記錄學習《圖解設計模式》過程當中的心得體會,歡迎留言一塊兒交流想法~html
Adapt to Design Pattern——適應設計模式java
Left to subclass——交給子類node
Generating Instance——生成實例python
Consider Individualy——分開考慮mysql
Consistency——一致性git
Access Data Structure——訪問數據結構算法
Simplify——簡單化sql
Manage Status——管理狀態編程
Avoid wasting
Represent with Class——用類來表現
因爲每一個人的行文風格不一樣,所以表情達意的方式也不一樣,在本文中有着以下約定:
Client
類都表示業務類,即脫離於設計模式以外的,將設計模式應用於業務代碼的測試類。IDEA
插件PlantUML
所畫,可參考《碰見PantUML~》一文一般,優良的代碼設計須要遵循如下原則
每一個類的存在應該都只是爲了知足一個特定的需求,例如Collection
類中的方法應該都是爲了維護內部元素的結構組織而存在,而應該將如何遍歷Collection
中元素的職責交給Iterator
。
單一職責保證專業的事交給專業的人來作,這樣每一個類發生修改的緣由只會有一個(由於每一個類的責任只有一個),這樣就保證了後續如有需求變動只會致使負責解決該需求的類發生改變,而其餘的類均不會受到影響。改變越少,系統發生BUG的概率就會越小。
系統要對修改關閉,對擴展開放。代碼設計要儘可能避免對現有代碼的修改,由於一旦修改一處就可能致使依賴該類的其餘類發生改變,一旦改變,就有可能引入新的潛在的BUG。若是需求變動,代碼設計應該經過新增類(多爲實現類)的方式來知足新的需求,而客戶端代碼(依賴該類的其餘類)應該無需修改或只需少許修改。
里氏代換原則依託於OOP的多態性。在運行時,客戶端依賴的對象可被其餘「同源」的(有相同的父類或接口)對象替換而客戶端的調用邏輯不受任何影響,這要求咱們在聲明對象的外觀類型(聲明類型)時儘可能選擇高層次一些的類(類的層次結構)。
接口隔離原則要求咱們將接口方法按照接口功能分開定義在不一樣的接口中,例如createItertor()
應該定義在Iterable
中,fly()
應該定義在Flyable
之中,這樣可以減輕實現類的負擔,避免實現類被捆綁着要求實現沒必要要的接口方法。
同時,Java8以後,接口方法若是有通用實現應該定義爲default
依賴倒置原則要求咱們儘可能消除點對點的「強」依賴,而應該使二者依賴抽象,例如Controller
依賴AbstractService
中的抽象方法進行聲明式編程,XxxServiceImpl
則對AbstractService
的抽象方法進行實現,這樣就實現了控制器和具體業務處理類之間的解耦,一旦後續業務變動,咱們只須要新增一個XxxServiceImpl2
並藉助多態就可以輕鬆實現業務處理的切換。
本章將以Iterator
模式和Adaptor
模式兩個較爲簡單的做爲設計模式的入門,切身體會設計模式存在的價值和軟件開發中應遵循的一些原則。
Iterator
中譯「迭代器」,起逐個訪問集合中的元素的做用。在數據結構中組合元素的方式有不少,如數組、連表、哈希表、二叉樹等,根據集合的不一樣的組織形式,咱們遍歷訪問集合元素的方式也是不同的。這時咱們的業務代碼中的訪問邏輯(即遍歷到當前元素時須要幹什麼)和遍歷邏輯(須要知道集合內部結構)是耦合在一塊兒的,一旦集合換一種組織形式,那麼咱們的業務代碼也須要跟着改變。
例如,以下書架BookShelf
經過數組的形式組織了一些書Book
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private String name;
}
public class BookShelfWithArr {
private final Book[] books;
private final int size;
private int index;
public BookShelfWithArr(int size) {
this.size = size;
this.books = new Book[size];
this.index = 0;
}
public void put(Book book) {
if (book == null) {
throw new IllegalArgumentException("book can't be null");
}
if (index == size) {
throw new RuntimeException("the bookshelf is full");
}
books[index++] = book;
}
public Book get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("index should be equal or big than 0 but less than " + size);
}
return books[index];
}
public int size() {
return this.size;
}
}
複製代碼
若是不使用Iterator
模式,那麼你的業務代碼可能以下所示:
public class Client {
public static void main(String[] args) {
BookShelfWithArr bookShelf = new BookShelfWithArr(4);
bookShelf.put(new Book("Java"));
bookShelf.put(new Book("C"));
bookShelf.put(new Book("php"));
bookShelf.put(new Book("python"));
for (int i = 0; i < bookShelf.size(); i++) {
System.out.println(bookShelf.get(i));
}
}
}
複製代碼
這是咱們初學Java集合章節時司空見慣的代碼。如今咱們來考慮一個問題,假設這個書架被打形成可伸縮的,便可以根據咱們所放書籍數量而變大變小,此時咱們該怎麼辦?
BookShelf
好說,根據可擴容的特性,咱們能夠應用ArrayList
來代替數組
@Data
public class BookShelf {
private ArrayList<Book> bookList;
public BookShelf() {
this.bookList = new ArrayList<>();
}
public BookShelf(ArrayList<Book> bookList) {
this.bookList = bookList;
}
}
複製代碼
如此的話,咱們的遍歷訪問邏輯就要作出相應調整
BookShelf bookShelf2 = new BookShelf();
bookShelf2.getBookList().add(new Book("Java"));
bookShelf2.getBookList().add(new Book("C"));
bookShelf2.getBookList().add(new Book("php"));
bookShelf2.getBookList().add(new Book("python"));
for (int i = 0; i < bookShelf2.getBookList().size(); i++) {
System.out.println(bookShelf2.getBookList().get(i));
}
複製代碼
這裏一旦集合改變組織元素的方式,任何其餘存在遍歷該集合對象的代碼(相對於該集合來講,這些代碼稱爲客戶端代碼)都須要跟着改變。意味着,客戶端代碼和集合是緊耦合的,客戶端代碼不該該關心集合內部是如何組織元素的,而只應該關心遍歷該集合拿到元素以後應該作什麼。
因而咱們用Iterator
模式來改造一下
首先不管集合如何組織元素,它都應該是可遍歷的,所以須要抽象出兩個方法:
boolean hasNext
——集合中是否還有未遍歷的元素E next()
——取出下一個未遍歷的元素public interface Iterator<E> {
boolean hasNext();
E next();
}
複製代碼
集合應該只關注如何組織元素,所以應該將上述遍歷邏輯交由他人Iterator
來作
public interface Iterable<E> {
Iterator<E> iterator();
}
複製代碼
經過調用BookShelf
的iterator
方法咱們能夠獲取BookShelf
的迭代器,經過使用該迭代器的方法能夠實現對BookShelf
中元素的遍歷訪問:
public class BookShelfIterator implements Iterator<Book> {
private int index;
private BookShelf bookShelf;
public BookShelfIterator(BookShelf bookShelf) {
this.index = 0;
this.bookShelf = bookShelf;
}
@Override
public boolean hasNext() {
return index < bookShelf.getBookList().size();
}
@Override
public Book next() {
if (!hasNext()) {
throw new RuntimeException("you have arrived the end")
}
return bookShelf.getBookList().get(index++);
}
}
@Data
public class BookShelf implements Iterable<Book> {
private ArrayList<Book> bookList;
public BookShelf() {
this.bookList = new ArrayList<>();
}
public BookShelf(ArrayList<Book> bookList) {
this.bookList = bookList;
}
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
複製代碼
客戶端代碼:
public class IteratorClient {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf();
bookShelf.getBookList().add(new Book("Java"));
bookShelf.getBookList().add(new Book("C"));
bookShelf.getBookList().add(new Book("php"));
bookShelf.getBookList().add(new Book("python"));
Iterator<Book> iterator = bookShelf.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
複製代碼
如此,若是BookShelf
換用Map
來組織元素,咱們只需新增一個BookShelfMapIterator
便可,而客戶端代碼無需任何改動:
@Data
public class MapBookShelf implements Iterable{
/** * book's name -> book */
private Map<String, Book> bookMap = new HashMap<>();
@Override
public Iterator iterator() {
return new MapBookShelfIterator(this);
}
}
複製代碼
public class MapBookShelfIterator implements Iterator {
private final MapBookShelf mapBookShelf;
private final Object[] keys;
private int index;
public MapBookShelfIterator(MapBookShelf mapBookShelf) {
this.mapBookShelf = mapBookShelf;
this.keys = mapBookShelf.getBookMap().keySet().toArray();
this.index = 0;
}
@Override
public boolean hasNext() {
return index < keys.length;
}
@Override
public Object next() {
if (!hasNext()) {
throw new RuntimeException("you have arrived the end");
}
return mapBookShelf.getBookMap().get(keys[index++]);
}
}
複製代碼
MapBookShelf bookShelf = new MapBookShelf();
bookShelf.getBookMap().put("Java", new Book("Java"));
bookShelf.getBookMap().put("C",new Book("C"));
bookShelf.getBookMap().put("PHP",new Book("php"));
bookShelf.getBookMap().put("Python", new Book("python"));
Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
複製代碼
雖然上述客戶端代碼也發生了改變,如getBookMap
、put
(這些改變能夠經過抽象出一個AbstractBookShelf
來避免),可是遍歷訪問邏輯沒變,即首先取得集合的Iterator
實例,而後調用接口方法hasNext
和next
進行遍歷,hasNext
是對遍歷界限的一個控制,其自己不作任何事,僅判斷當前位置是否有元素;而next
則作兩件事:返回當前位置上的元素,將遍歷指針後移。
完整的Iterator
模式可表述以下
其中Iterable
和Iterator
對應,ConcreteIterable
和ConcreteIterator
對應,客戶端僅知道Iterator
和Iterable
中的3個方法,可用此實現集合的遍歷而無論集合內部組織形式,不一樣的集合實例則將其對應的ConcreteIterator
實現隱藏在了createIterator
方法中
Iterator
,迭代器,專門負責迭代集合,符合單一職責
boolean hasNext()
E next()
Iterable
,集合須要實現該接口,將遍歷責任委託給具體的迭代器實例
Iterator createIterator()
Collection
,集合ConcreteIterator
,具體的迭代器,和具體的集合實例之間是相互依賴的關係Adapter
是爲了將已有的實現適應不一樣的接口而存在。生活中的典型例子是,爲了能使兩個插銷插頭查到三個插孔的插座上,一般會在二者之間加上一個插口轉換器,這個轉換器承擔的角色就是本設計模式的用意。
爲了使已有的方法適應新的接口(例如已有方法健壯沒有毛病,針對相同功能的新接口咱們又不像寫重複代碼),咱們一般會編寫一個Adapter
,它僅僅起着一個轉換器的做用。
Adapter
能夠經過繼承和委託兩種方式實現
例如,系統中遺留着他人已寫好的字符串打印類Banner
public class Banner {
public void printWithBracket(String s) {
System.out.println("(" + s + ")");
}
public void printWithStar(String s) {
System.out.println("*" + s + "*");
}
}
複製代碼
如今你正在對系統迭代,須要爲新的接口Print
編寫實現類
public interface Print {
void printWeak(String s);
void printStrengthen(String s);
}
複製代碼
而你的實現邏輯和Banner
中的兩個已有方法不謀而和,因而你可經過繼承舊類、實現新接口的方式,既能避免重複代碼的編寫,又對新接口有所交代
public class PrintBanner extends Banner implements Print {
@Override
public void printWeak(String s) {
this.printWithBracket(s);
}
@Override
public void printStrengthen(String s) {
this.printWithStar(s);
}
}
複製代碼
此種方式的缺點是,若目標類(這裏指
你還能夠經過聚合的方式,將實現邏輯委託給舊類:
public class CustomPrint implements Print {
private Banner banner;
public CustomPrint(Banner banner) {
this.banner = banner;
}
@Override
public void printWeak(String s) {
banner.printWithBracket(s);
}
@Override
public void printStrengthen(String s) {
banner.printWithStar(s);
}
}
複製代碼
如下是兩種方式的類圖
其中方式1受目標類必須是接口的限制。
Adaptee
,被適配方,一般爲系統中的舊類,且類中存在對某功能A的實現Target
,目標類,一般爲新添加到系統中的類,包含須要實現某功能A的抽象方法Adapter
,適配類,做爲被適配方和目標類之間的橋樑,經過繼承或聚合的方式實現代碼複用模板方法模式屬於特殊的「聲明式」編程,即經過抽象定義一個業務處理邏輯中各步驟的前後執行順序,但對於各步驟的具體實現並不關心,交由運行時實際的子類對象受理。這也充分利用了OOP的多態性
例如,現有一個訂單業務類OrderService
以下:
public class OrderService {
public void makeOrder() {
safeVerification();
reduceStock();
reduceBalance();
noticeDelivery();
}
public void safeVerification() {
System.out.println("安全校驗");
}
public void reduceStock() {
System.out.println("去MySQL減庫存");
}
public void reduceBalance() {
System.out.println("去MySQL減餘額");
}
public void noticeDelivery() {
System.out.println("通知發貨");
}
}
複製代碼
客戶端代碼以下
public class Client {
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.makeOrder();
}
}
複製代碼
上述代碼邏輯清晰,看起來沒什麼問題。但假設如今要作秒殺,須要將庫存、餘額等信息作一個緩存,那你就須要在原有的OrderService
上作修改了。這違反了「開閉原則」(應對修改關閉而對擴展開放)。
此時咱們就須要將業務步驟抽象出來,具體的實現交由特定的子類去作,以知足不一樣的業務場景:
public abstract class AbstractOrderService {
public void makeOrder() {
safeVerification();
reduceStock();
reduceBalance();
noticeDelivery();
}
public void safeVerification(){
System.out.println("安全校驗");
}
public abstract void reduceStock() ;
public abstract void reduceBalance();
public void noticeDelivery(){
System.out.println("通知發貨");
}
}
複製代碼
此時若須要緩存支持,只需新增一個實現類便可
public class OrderServiceWithCache extends AbstractOrderService {
@Override
public void reduceStock() {
System.out.println("從緩存中減庫存");
}
@Override
public void reduceBalance() {
System.out.println("從緩存中減餘額");
}
}
複製代碼
客戶端代碼只需切換具體的實現類:
public class Client {
public static void main(String[] args) {
AbstractOrderService orderService = new OrderServiceWithCache(); //使用緩存
orderService.makeOrder();
orderService = new OrderService(); // 切換到MySQL
orderService.makeOrder();
}
}
複製代碼
模板方法模式就是在抽象類中進行聲明式編程(使用抽象方法的形式強調該業務的完成應該執行哪些步驟以及這些步驟的執行順序,而不關注每一個步驟具體是如何實現的),而將具體業務步驟的實現交由子類(運行時經過多態)完成。
在客戶端看來,雖然只是切換了一會兒類實例,但好像被切換的實例實現的具體步驟就被注入到總體地業務處理之中同樣。
而且對於通用的步驟,如上述的safeVerification
和noticeDelivery
,可能它們的處理邏輯是固定的,這時能夠將它們提取到父類中,實現複用。
SuperClass & Template Method
,模板方法一般聲明在抽象類或接口中,調用本類的抽象方法(固然也能夠是包含通用邏輯的非抽象方法)完成特定的業務邏輯。Concrete SubClass & Realization
,子類只需實現相應的抽象方法就能夠將具體的功能步驟注入到總體業務功能中,無需本身顯式調用(模板方法會根據運行時信息動態調用)工廠方法模式就是模板方法模式的一個應用,只不過就是抽象父類中的抽象方法特化爲一個建立實例的方法,將實例的建立延遲到了子類。
例如Person
類有一個獲取本身交通工具的抽象方法getVehicle
,而且可以在其餘地方調用該Vehicle
暴露的屬性、方法,而將Vehicle
實例的獲取延遲到了子類(說是延遲,由於自己是抽象類,是沒法被實例化的,所以在實例化Person
的具體子類時可以確保其getVehicle
已被重寫了)。
以下是示例代碼:
public class Vehicle {
private String name;
public Vehicle(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Bicycle extends Vehicle {
public Bicycle() {
super("自行車");
}
}
public class JeepCar extends Vehicle {
public JeepCar() {
super("小汽車");
}
}
public abstract class Person {
public void useVehicle() {
System.out.println("使用交通工具"+getVehicle().getName()+"來代步");
}
protected abstract Vehicle getVehicle();
}
public class Student extends Person {
@Override
protected Vehicle getVehicle() {
return new Bicycle();
}
}
public class Boss extends Person {
@Override
protected Vehicle getVehicle() {
return new JeepCar();
}
}
public class Client {
public static void main(String[] args) {
Person p = new Student();
p.useVehicle();
p = new Boss();
p.useVehicle();
}
}
複製代碼
Factory Method
,抽象父類中將某類實例的獲取延遲到具體子類,但本類中的其餘方法能夠調用該方法並認爲已獲取到了該類實例,而後進行屬性、方法的訪問Concrete Method
,真正的獲取並返回所需實例的邏輯Lazy loading
懶加載模式,instance
會在getInstance
第一次被調用時被初始化
public class LazyLoadingSingleton {
private LazyLoadingSingleton() {
}
private static LazyLoadingSingleton instance;
public static LazyLoadingSingleton getInstance() {
if (instance == null) {
instance = new LazyLoadingSingleton();
}
return instance;
}
}
複製代碼
Synchronized Block Singleton
上述代碼在多線程併發執行時會出現instance
被屢次賦值的問題,爲此可用內部鎖語義Synchronized
解決
public class SynchronizerBlockSingleton {
private SynchronizerBlockSingleton() {
}
private static SynchronizerBlockSingleton instance;
public static SynchronizerBlockSingleton getInstance() {
synchronized (SynchronizerBlockSingleton.class) {
if (instance == null) {
instance = new SynchronizerBlockSingleton();
}
}
return instance;
}
}
複製代碼
Double-Checked Lock
雙重檢查鎖定,上述代碼先鎖定,而後檢查,會致使instance
被賦值後synchronized
對後續併發調用getInstance
帶來上下文切換的開銷,爲此能夠在鎖定前先檢查一次
public class DoubleCheckedSingleton {
private DoubleCheckedSingleton() {
}
private static DoubleCheckedSingleton instance;
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
複製代碼
DCL with volatile
因爲指令重排序可能會致使new
關鍵字初始化對象還未完成就返回對象的內存地址,進而致使後續訪問instance
屬性時拋空指針異常,須要使用volatile
保證對象初始化完畢後才返回引用地址
public class VolatileSingleton {
private VolatileSingleton() {
}
private static volatile VolatileSingleton instance;
public static VolatileSingleton getInstance() {
if (instance == null) {
synchronized (VolatileSingleton.class) {
if (instance == null) {
instance = new VolatileSingleton();
}
}
}
return instance;
}
}
複製代碼
Eager Mode
餓漢模式,若是實例對象的初始化開銷較小(佔用內存、初始化時間),那麼徹底能夠在類初始化時完成
public class EagerSingleton {
private EagerSingleton() {
}
private static EagerSingleton instance = new EagerSingleton();
public static EagerSingleton getInstance() {
return instance;
}
}
複製代碼
instance
會在類初始化時被初始化,類只會在發生主動引用時被初始化一次,由JVM來保證
Instance Holder
若是你仍想使用懶漢模式又想優雅些,則可以使用靜態內部類的方式
public class InstanceHolderSingleton {
private static class SingletonHolder {
private static InstanceHolderSingleton instance = new InstanceHolderSingleton();
}
private InstanceHolderSingleton() {
}
public static InstanceHolderSingleton getInstance() {
return SingletonHolder.instance;
}
}
複製代碼
初始化InstanceHolderSingleton
時並不會初始化其靜態內部類SingletonHolder
,只有在調用InstanceHolderSingleton.getInstance()
時,instance
纔會隨着SingletonHolder
的初始化而初始化。
如下種狀況會當即致使類的初始化:
new
關鍵字建立該類實例java.reflect
包下的類反射訪問該類,如Class.forName
枚舉類的優雅
JVM也會保證枚舉實例在初始化枚舉時被初始化一次
public class EnumSingleton {
private EnumSingleton() {
}
private static EnumSingleton instance;
private enum InstanceEnum{
INSTANCE;
private EnumSingleton instance;
InstanceEnum() {
instance = new EnumSingleton();
}
}
public static EnumSingleton getInstance() {
return InstanceEnum.INSTANCE.instance;
}
}
複製代碼
建造者模式一般應用於須要經過一系列複雜步驟才能獲得最終實例的狀況。就像建造房子同樣,咱們須要通過打地基、搭建框架、添磚加瓦、粉飾美化等一系列步驟才能獲得最終能住人的房子。而且,這些步驟能夠個性化定製,例若有人喜歡歐美風格的,那麼房屋框架頂部就要打形成錐形的;有人喜歡粉色,那就能夠鋪上粉色的牆紙……
本例中,咱們以一個郵件內容String
實例的生成來演示Builder
設計模式的應用。
public abstract class EmailBuilder {
protected String content = "";
public String getContent() {
return content;
}
public abstract void makeTitle(String title);
public abstract void makeBody(String body);
public abstract void makeGreeting(String greeting);
}
public class TextEmailBuilder extends EmailBuilder {
@Override
public void makeTitle(String title) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(title).append("\n");
content = stringBuilder.toString();
}
@Override
public void makeBody(String body) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(body).append("\n");
content = stringBuilder.toString();
}
@Override
public void makeGreeting(String greeting) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(greeting).append("\n");
content = stringBuilder.toString();
}
}
public class HTMLEmailBuilder extends EmailBuilder {
@Override
public void makeTitle(String title) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<h3>").append(title).append("</h3>").append("\n");
content = stringBuilder.toString();
}
@Override
public void makeBody(String body) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<p style=\"font-family: Microsoft Ya Hei; font-size: 16px\">").append(body).append("</p>").append("\n");
content = stringBuilder.toString();
}
@Override
public void makeGreeting(String greeting) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<i>").append(greeting).append("</i>").append("\n");
content = stringBuilder.toString();
}
}
public class Director {
private EmailBuilder emailBuilder;
public Director(EmailBuilder emailBuilder) {
this.emailBuilder = emailBuilder;
}
public void construct() {
emailBuilder.makeTitle("About Interview");
emailBuilder.makeBody("We are honor to tell you that you can participate our interview.");
emailBuilder.makeGreeting("Good Luck!");
}
}
複製代碼
其中EmailBuilder
中聲明瞭實例的初始狀態(空串)和構建實例的一系列過程,而TextEmailBuilder
和HTMLEmailBuilder
則對這一系列過程進行了個性化實現。最終Director
是建造實例整個過程的監工,由它確保實例的成型規則地經歷了哪些建造過程。
Builder
,定義了產品成型須要通過的一系列工藝(接口方法)ConcreteBuilder
,針對每道工藝進行個性化處理Director
,監工,根據產品成型流程調用接口方法打造產品Client
,模式使用者,通知Director
根據傳入的ConcreteBuilder
打造特定風格的產品AbstractFactory
其實就是包含了一系列Factory Method
的類,只不過這些Factory Method
生成的實例都是相互關聯的,一塊兒組成某個共同體,少了誰都不行。
例如汽車Car
須要汽車外殼Facade
、輪胎Wheel
、發動機Engine
等部件,那麼咱們就能夠建立一個CarFactory
抽象工廠,其中聲明瞭一系列部件的獲取(抽象方法,不關心該部件是哪一個廠家生產的或是哪一個牌子的),並提供了產品的構造過程(調用這一系列抽象方法獲取所需部件組裝成車)
public class Engine {
String name;
public Engine(String name) {
this.name = name;
}
}
複製代碼
public class Wheel {
String name;
public Wheel(String name) {
this.name = name;
}
}
複製代碼
public class Facade {
String name;
public Facade(String name) {
this.name = name;
}
}
複製代碼
public class Car {
Engine engine;
Wheel wheel;
Facade facade;
public Car(Engine engine, Wheel wheel, Facade facade) {
this.engine = engine;
this.wheel = wheel;
this.facade = facade;
}
}
複製代碼
public abstract class CarFactory {
public Car getCar() {
return new Car(getEngine(), getWheel(), getFacade());
}
public abstract Engine getEngine();
public abstract Wheel getWheel();
public abstract Facade getFacade();
}
複製代碼
public class CustomEngine extends Engine {
public CustomEngine() {
super("自定義牌發動機");
}
}
複製代碼
public class CustomWheel extends Wheel{
public CustomWheel() {
super("自定義牌輪胎");
}
}
複製代碼
public class CustomFacade extends Facade {
public CustomFacade() {
super("自定義牌車殼");
}
}
複製代碼
public class CustomCarFactory extends CarFactory{
@Override
public Engine getEngine() {
return new CustomEngine();
}
@Override
public Wheel getWheel() {
return new CustomWheel();
}
@Override
public Facade getFacade() {
return new CustomFacade();
}
}
複製代碼
public class Client {
public static void main(String[] args) {
CarFactory carFactory = new CustomCarFactory();
Car car = carFactory.getCar();
System.out.println("custom car -> " + car.engine.name + "+" + car.wheel.name + "+" + car.facade.name);
}
}
複製代碼
類的功能層次結構
經過繼承咱們可以繼承基類已有的功能,在此之上咱們可以:重寫基類已有功能(使該功能具有本類特點)、也能夠新增功能,重寫(這裏的重寫特指重寫基類的已有實現)或新增功能的子類與基類構成類的功能層次結構
public class Animal {
public void eat() {
System.out.println("動物會覓食");
}
}
public class Bird extends Animal {
@Override
public void eat() {
System.out.println("鳥覓食蟲子");
}
public void fly() {
System.out.println("鳥會飛");
}
}
複製代碼
類的實現層次結構
經過繼承,咱們可以實現基類的抽象方法,經過運用Template Method
,咱們能夠將子類的邏輯注入到基類模板過程當中,這時新增子類僅爲了實現基類的抽象方法,子類和基類構成類的實現層次結構
public abstract class Animal {
public void hunt() {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget();
public abstract void quickAttack();
public abstract void swallow();
}
public class Snake extends Animal {
@Override
public void lockTarget() {
System.out.println("鎖定獵物");
}
@Override
public void quickAttack() {
System.out.println("迅速咬住獵物喉部");
}
@Override
public void swallow() {
System.out.println("一口吞掉整個獵物");
}
}
複製代碼
若是咱們將兩個例子的Animal
整合在一塊兒:
public abstract class Animal {
public void eat() {
System.out.println("動物會覓食");
}
public void hunt() {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget();
public abstract void quickAttack();
public abstract void swallow();
}
複製代碼
你會發現,Bird
沒法編譯,做爲具體子類它必須實現抽象方法lockTarget
、quickAttack
、swallow
,可是咱們新增Bird
的初衷只是爲了繼承Animal
的eat
方法,並新增一個本身會fly
的功能。
這時就須要咱們將類的功能層次和實現層次分開了
public abstract class Animal {
private Hunt hunt;
public Animal(Hunt hunt) {
this.hunt = hunt;
}
public void eat() {
System.out.println("動物會覓食");
}
public void hunt() {
hunt.hunt();
}
}
public abstract class Hunt {
public void hunt() {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget();
public abstract void quickAttack();
public abstract void swallow();
}
public class Bird extends Animal {
public Bird(Hunt hunt) {
super(hunt);
}
@Override
public void eat() {
System.out.println("鳥覓食蟲子");
}
public void fly() {
System.out.println("鳥會飛");
}
}
public class DefaultHunt extends Hunt {
@Override
public void lockTarget() {
System.out.println("用眼睛鎖定獵物");
}
@Override
public void quickAttack() {
System.out.println("快速咬死獵物");
}
@Override
public void swallow() {
System.out.println("一口一口吃掉獵物");
}
}
public class Snake extends Animal {
public Snake(Hunt hunt) {
super(hunt);
}
}
public class SnakeHunt extends Hunt {
@Override
public void lockTarget() {
System.out.println("紅外線感知鎖定獵物");
}
@Override
public void quickAttack() {
System.out.println("使用尖牙和毒液快速致死獵物");
}
@Override
public void swallow() {
System.out.println("一口吞掉整個獵物");
}
}
public class Client {
public static void main(String[] args) {
Hunt defaultHunt = new DefaultHunt();
Hunt snakeHunt = new SnakeHunt();
Animal snake = new Snake(snakeHunt);
System.out.println("蛇開始狩獵==========");
snake.hunt();
Animal bird = new Bird(defaultHunt);
System.out.println("鳥開始狩獵===========");
bird.hunt();
System.out.println("鳥有不一樣於通常動物的功能");
((Bird) bird).fly();
}
}
複製代碼
如上,Animal
、Bird
、Snake
組成功能層次結構、Hunt
、DefaultHunt
、Snake
則組成了實現層次結構。這樣,之後若是咱們想擴展功能(重寫或新增),那麼就能夠找對應功能層次結構中的類繼承;若是想針對狩獵方式進行個性化實現,則繼承實現層次結構中的類便可。
橋接模式避免了將實現和擴展捆綁在一塊兒,減小底層類擴展基類的壓力
假如你是一個農場主,須要按照顧客的需求從已採摘的蘋果中挑選出符合顧客標準的蘋果
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Apple {
private AppleColorEnum color;
private double weight;
public enum AppleColorEnum {
RED,YELLOW,GREEN
}
}
複製代碼
因而你編寫了以下挑選蘋果的業務處理類
public class AppleService {
List<Apple> findAppleByColor(List<Apple> apples, Apple.AppleColorEnum color) {
List<Apple> res = new ArrayList<>();
for (Apple apple : apples) {
if (Objects.equals(apple.getColor(),color)){
res.add(apple);
}
}
return res;
}
}
複製代碼
可是若是你遇到了一個刁鑽的顧客,他不只要求顏色爲紅色,並且還要求重量在500g以上呢?你能夠再添加一個findRedAndWeightGreatThan500
,可是每一個顧客可能對顏色和重量的標準都是不同的,而且你沒法估量有哪些顧客,對應有哪些挑選標準。
這時就須要把挑選標準(實際上就是一個算法)單獨抽離出來,分析輸入和輸出:
Apple
,給你一個蘋果boolean
,該蘋果是否符合標準public interface AppleFilterStrategy {
/** * 若是該蘋果符合挑選標準,那麼就返回true * @param apple * @return */
boolean filterApple(Apple apple);
}
複製代碼
這時,挑選蘋果業務類就無需預知和預置衆多挑選蘋果的方法了,由於挑選策略交給了AppleFilterStrategy
public class AppleService {
List<Apple> findApple(List<Apple> apples, AppleFilterStrategy strategy) {
List<Apple> res = new ArrayList<>();
for (Apple apple : apples) {
if (strategy.filterApple(apple)) {
res.add(apple);
}
}
return res;
}
}
複製代碼
客戶端能夠根據客戶提出的挑選需求,經過匿名類的方式隨意地注入挑選策略
public class Client {
public static void main(String[] args) {
// 農場主採摘的蘋果
List<Apple> apples = Arrays.asList(
new Apple(Apple.AppleColorEnum.RED, 200),
new Apple(Apple.AppleColorEnum.RED, 400),
new Apple(Apple.AppleColorEnum.RED, 600),
new Apple(Apple.AppleColorEnum.YELLOW, 100),
new Apple(Apple.AppleColorEnum.YELLOW, 500),
new Apple(Apple.AppleColorEnum.YELLOW, 900),
new Apple(Apple.AppleColorEnum.GREEN, 400),
new Apple(Apple.AppleColorEnum.GREEN, 500),
new Apple(Apple.AppleColorEnum.GREEN, 600)
);
AppleService appleService = new AppleService();
// A顧客須要紅色的重量大於500g的蘋果
List<Apple> res1 = appleService.findApple(apples, new AppleFilterStrategy() {
@Override
public boolean filterApple(Apple apple) {
return Objects.equals(apple.getColor(), Apple.AppleColorEnum.RED) &&
apple.getWeight() >= 500;
}
});
System.out.println(res1);
System.out.println("======================");
// B顧客須要青色的種類小於400的
List<Apple> res2 = appleService.findApple(apples, new AppleFilterStrategy() {
@Override
public boolean filterApple(Apple apple) {
return Objects.equals(apple.getColor(), Apple.AppleColorEnum.GREEN) &&
apple.getWeight() <= 400;
}
});
System.out.println(res2);
}
}
複製代碼
在Java8以後,像AppleFilterStrategy
這種只包含一個接口方法的接口能夠被標註爲@FunctionalInterface
,並使用Lambda
表達式替代冗餘的匿名類
@FunctionalInterface
public interface AppleFilterStrategy {
boolean filterApple(Apple apple);
}
複製代碼
List<Apple> res3 = appleService.findApple(apples, apple -> apple.getColor() != Apple.AppleColorEnum.GREEN && apple.getWeight() >= 300);
System.out.println(res3);
複製代碼
策略模式的核心思想就是將複雜多變的算法邏輯抽取出來,交給客戶端實現,而業務層只負責應用客戶端傳遞的算法實現
Strategy Interface
,策略接口,定義了某個特定的算法Service
,應用策略接口的算法,面對抽象編程Client
,調用Service
時注入具體的算法實現Concrete Strategy
,具體的算法實現,一般以匿名類的形式存在,Java8以後可用Lambda
代替混合模式的典型應用就是文件系統,一個目錄Directory
中能夠存放若干條目Entry
,每一個條目既能夠是目錄又能夠是文件File
。混合模式的目的就是讓容器(如目錄)和容器中的內容(如目錄或文件)有着一致性的外觀(如Entry
)
public abstract class Entry {
private String name;
private int size;
private List<Entry> items;
public Entry(String name, int size) {
this.name = name;
this.size = size;
items = new ArrayList<>();
}
public void addEntry(Entry entry) {
this.items.add(entry);
}
public abstract void print(int... layer);
}
public class Directory extends Entry {
public Directory(String name, int size) {
super(name, size);
}
@Override
public void print(int... layer) {
int n;
if (layer == null || layer.length == 0) {
n = 0;
} else {
n = layer[0];
}
for (int i = 0; i < n; i++) {
System.out.print("\t");
}
System.out.println(getName());
getItems().forEach(entry -> entry.print(n + 1));
}
}
public class File extends Entry {
public File(String name, int size) {
super(name, size);
}
@Override
public void print(int... layer) {
int n;
if (layer == null || layer.length == 0) {
n = 0;
} else {
n = layer[0];
}
for (int i = 0; i < n; i++) {
System.out.print("\t");
}
System.out.println(getName() + " size=" + getSize()+"kb");
getItems().forEach(entry -> entry.print(n + 1));
}
}
public class Client {
public static void main(String[] args) {
Entry root = new Directory("/root", 2);
Entry bin = new Directory("/bin", 0);
root.addEntry(bin);
Entry usr = new Directory("/usr", 1);
root.addEntry(usr);
Entry etc = new Directory("/etc", 0);
root.addEntry(etc);
Entry local = new Directory("/local", 3);
usr.addEntry(local);
Entry java = new File("java.sh", 128);
local.addEntry(java);
Entry mysql = new File("mysql.sh", 64);
local.addEntry(mysql);
Entry hadoop = new File("hadoop.sh", 1024);
local.addEntry(hadoop);
root.print();
}
}
/root
/bin
/usr
/local
java.sh size=128kb
mysql.sh size=64kb
hadoop.sh size=1024kb
/etc
複製代碼
組件
如本例的Entry
,是容器和容器內容的同一外觀。系統不直接操做容器和容器中的內容,而是操做組件
容器
其中可包含若干容器和條目
條目
系統中的基本單元
裝飾者模式是一種結合繼承和組合(委託)設計模式,經過繼承可以實現統一外觀(裝飾類和目標類有共同的父類),經過委託可以在目標功能的基礎之上進行加強。而且裝飾類無論目標類是源目標類仍是被裝飾過的目標類,它對目標類是否已被保存過和被哪一種包裝器(裝飾類)包裝過以及被包裝了幾層都不關心,它只負責當前的裝飾邏輯。
本例中,有一個糕點師Baker
,他有一個bake
烘焙麪包的抽象方法,BreadBaker
則是他的一個實現。如今咱們須要根據顧客的不一樣口味對原味的麪包進行包裝,例如應該加哪些佐料Ingredient
,如下是示例代碼
public abstract class Baker {
public abstract void bake();
}
public class BreadBaker extends Baker {
@Override
public void bake() {
System.out.println("麪包被烘焙");
}
}
複製代碼
public class IngredientDecorator extends Baker {
private Baker baker;
private String ingredient;
public IngredientDecorator(Baker baker,String ingredient) {
this.baker = baker;
this.ingredient = ingredient;
}
@Override
public void bake() {
System.out.print("添加了" + ingredient + "的");
baker.bake();
}
}
複製代碼
public class Client {
public static void main(String[] args) {
Baker baker = new BreadBaker();
baker.bake();
Baker pepper = new IngredientDecorator(baker, "胡椒粉");
pepper.bake();
Baker mustard = new IngredientDecorator(baker, "芥末");
mustard.bake();
Baker oliveOil = new IngredientDecorator(baker, "橄欖油");
oliveOil.bake();
Baker pepperAndOlive = new IngredientDecorator(new IngredientDecorator(baker, "橄欖油"), "胡椒粉");
pepperAndOlive.bake();
Baker mustardAndOliveAndPepper =
new IngredientDecorator(
new IngredientDecorator(
new IngredientDecorator(baker, "胡椒粉"),
"橄欖油"),
"芥末");
mustardAndOliveAndPepper.bake();
}
}
麪包被烘焙
添加了胡椒粉的麪包被烘焙
添加了芥末的麪包被烘焙
添加了橄欖油的麪包被烘焙
添加了胡椒粉的添加了橄欖油的麪包被烘焙
添加了芥末的添加了橄欖油的添加了胡椒粉的麪包被烘焙
複製代碼
固然本例只是單純演示裝飾者模式的思想,你徹底能夠將Ingredient
具體化爲PepperIngredient
、MustardIngredident
等代替字符串魔法值
應用裝飾者模式有一個口訣:是你(IS-A)還有你(HAS-A),一切拜託你(全部的重寫方法委託給目標類對象,在此之上本身能夠添加當前這一層包裝的邏輯)。
Parent
,裝飾類和目標類有一個共同的父類,經過在父類聲明抽象方法使得二者有共同的外觀Target
,目標類,須要被包裝的類Decorator
,裝飾類,既能夠直接裝飾目標對象,又能夠裝飾裝飾過目標對象的裝飾對象,由於二者有共同的外觀visitor
模式目的是將數據結構的管理(對元素的增刪改查)和對數據結構的訪問處理邏輯分離開,一般和迭代器模式結合使用。咱們將對數據訪問處理的邏輯單獨定義一個Visitor
接口以及聲明相應的visit(E element)
方法,而數據接口則對應提供一個受理Visitor
的accept(Visitor visitor)
方法(其實就是簡單的調用visitor.visit()
。visit(E element)
至關於對訪問到的元素進行消費。
如下是數據結構爲多叉樹時的示例代碼(其中迭代器用的是前文實現的而非JDK自帶的)
@Data
public abstract class Node<E> implements Iterable<Node<E>> {
private E element;
private List<Node<E>> children;
public Node(E element) {
this.element = element;
}
public abstract void accept(NodeVisitor<E> visitor);
@Override
public Iterator<Node<E>> iterator() {
return new NodeIterator(this);
}
}
public class Leaf<E> extends Node<E> {
public Leaf(E element) {
super(element);
this.setChildren(Collections.emptyList());
}
@Override
public void accept(NodeVisitor<E> visitor) {
visitor.visitLeaf(this);
}
}
public class Branch<E> extends Node<E> {
public Branch(E elemnt) {
super(elemnt);
this.setChildren(new ArrayList<>());
}
@Override
public void accept(NodeVisitor<E> visitor) {
visitor.visitBranch(this);
}
}
複製代碼
public class NodeIterator<E> implements Iterator<Node<E>> {
private Node<E> root;
private Stack<Node<E>> stack;
public NodeIterator(Node<E> root) {
this.root = root;
this.stack = new Stack<>();
stack.push(root);
}
@Override
public boolean hasNext() {
return stack.size() > 0;
}
@Override
public Node<E> next() {
if (!hasNext()) {
throw new RuntimeException("no more elements");
}
Node<E> node = stack.pop();
List<Node<E>> children = node.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
return node;
}
}
複製代碼
public interface NodeVisitor<E> {
void visitLeaf(Leaf<E> leaf);
void visitBranch(Branch<E> branch);
}
public class PrintNodeVisitor<E> implements NodeVisitor<E> {
@Override
public void visitLeaf(Leaf<E> leaf) {
System.out.print(leaf.getElement()+" ");
}
@Override
public void visitBranch(Branch<E> branch) {
System.out.print(branch.getElement()+" ");
}
}
public class PlusOneNodeVisitor implements NodeVisitor<Integer> {
/** * 訪問到葉子節點則將節點值+1 * @param leaf */
@Override
public void visitLeaf(Leaf<Integer> leaf) {
leaf.setElement(leaf.getElement() + 1);
}
/** * 訪問到分叉節點,則將節點值+其孩子節點數 * @param branch */
@Override
public void visitBranch(Branch<Integer> branch) {
branch.setElement(branch.getElement() + branch.getChildren().size());
}
}
複製代碼
visitor
模式將對數據結構的訪問邏輯經過accept
委託給Visitor
接口和迭代器模式將遍歷訪問邏輯經過createIterator
委託給Iterator
接口有殊途同歸之妙。將專業的事交給專業的人作,知足Single Responsibility
和Interface Segregation Principle
;須要修改訪問處理邏輯咱們只須要新增一個NodeVisitor
的實現,知足Open/Closed Principle
;邏輯實現和客戶端代碼都面向接口NodeVisitor
編程,知足Dependency Inversion
。
Visitor
,拜訪者,聲明相關的visit
方法,用於對數據結構進行訪問處理DataStructure
,數據結構,提供元素的增刪改查接口,經過accept(visitor)
將訪問處理邏輯交給Visitor
ConcreteVisitor
,根據業務所需,實現具體的訪問處理邏輯責任鏈模式經過維護若干請求受理者造成一條鏈來處理客戶端發起的請求,該模式最大的優勢在於它弱化了客戶端和請求受理者之間的關係,客戶端只須要將請求發送到責任鏈,在責任鏈中經過連環委託的機制就可以作到沒法受理請求的人直接忽略請求,而可以受理請求的人截斷請求並受理。
客戶端不用關心請求具體會被誰受理,這樣就提升了客戶端的獨立性。
本例中,存在一條公司組織架構責任鏈(Employee->Leader->Manager->Boss
),他們都可以受理報銷費用的請求handle(int amount)
,Client
無需關心多少面值的報銷金額應該由誰來受理
public abstract class Handler {
private Handler handler = null;
public abstract void handle(int amount);
public Handler setNext(Handler handler) {
this.handler = handler;
return handler;
}
public Handler getNext() {
return handler;
}
}
public class Employee extends Handler {
@Override
public void handle(int amount) {
if (amount <= 100) {
System.out.println("$" + amount + " is handled by employee");
return;
}
this.getNext().handle(amount);
}
}
public class Leader extends Handler{
@Override
public void handle(int amount) {
if (amount <= 1000) {
System.out.println("$" + amount + " is handled by leader");
return;
}
this.getNext().handle(amount);
}
}
public class Manager extends Handler{
@Override
public void handle(int amount) {
if (amount <= 5000) {
System.out.println("$" + amount + " is handled by manager");
return;
}
this.getNext().handle(amount);
}
}
public class Boss extends Handler {
@Override
public void handle(int amount) {
System.out.println("$" + amount + " is handled by boss");
}
}
複製代碼
public class Client {
public static void main(String[] args) {
Handler employee = new Employee();
Handler leader = new Leader();
Handler manager = new Manager();
Handler boss = new Boss();
employee.setNext(leader).setNext(manager).setNext(boss);
for (int i = 0; i < 6; i++) {
int amount = (int) (Math.random() * 10000);
employee.handle(amount);
}
}
}
$6643 is handled by boss
$4964 is handled by manager
$684 is handled by leader
$9176 is handled by boss
$8054 is handled by boss
$909 is handled by leader
複製代碼
《圖解設計模式》