抽象工廠模式
用途
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
From: Design Patterns: Elements of Reusable Object-Oriented Software
提供一個接口凭峡,使得當創(chuàng)建一系列的相關(guān)或者相互依賴的對象時,不需要一一指定它們具體的類。
例子
下面是一個圖形界面工具包的例子作瞄。像窗口、滾動條等組件危纫,支持不同的樣式宗挥。樣式可以由界面的不同主題決定。這樣种蝶,如果在整個應用中寫死不同主題的不同按鈕樣式契耿,非常不好維護。
為了解決這個問題螃征,使應用便于在各個主題之間切換移植搪桂。我們可以定義一個抽象的WidgetFactory類,在其中定義創(chuàng)建每一種組件的接口盯滚。同樣地踢械,對于每一種組件,我們也有一個抽象類魄藕,每一種具體主題下的組件都是這個類的子類内列。WidgetFactory類中創(chuàng)建組件的接口就是創(chuàng)建一個新的組件實例。
這樣背率,客戶端調(diào)用WidgetFactory類的接口來得到各種組件的實例话瞧,但是嫩与,客戶端并不知道獲得的具體組件類是哪個。因此交排,就實現(xiàn)了調(diào)用方和具體主題的解耦划滋。如下圖。
從這里可以看出埃篓,抽象工廠類主要用于產(chǎn)生一系列相關(guān)的類或者一類對象处坪。使用了抽象工廠之后,如果想要切換獲得對象的類別或者系列都许,只需要改動抽象工廠的實例即可稻薇。通過這樣,就改變了所有獲得對象的主題胶征,從而實現(xiàn)了調(diào)用方和具體對象的解耦塞椎。
說明
AbstractFactory defers creation of product objects to its ConcreteFactory subclass.
From: Design Patterns: Elements of Reusable Object-Oriented Software
抽象工廠將產(chǎn)品對象的創(chuàng)建下放到了具體的工廠子類中。
脫離了具體的場景之后睛低,抽象工廠類的示意圖如下:
使用抽象工廠案狠,可以用來控制產(chǎn)品的一致性。當多個產(chǎn)品被設置為一個系列之后钱雷,應用使用同一個系列內(nèi)的產(chǎn)品是很重要的骂铁。抽象工廠模式使這種控制變得容易。
使用抽象工廠的壞處是罩抗,支持新的產(chǎn)品比較麻煩拉庵。因為在抽象工廠的接口中已經(jīng)固定了支持哪些產(chǎn)品,新增支持新的產(chǎn)品就需要修改這個接口套蒂。
例子
AbstractFactory.Cpu:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public abstract class Cpu {
private String name;
public Cpu(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
AbstractFactory.PhoneCpu:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class PhoneCpu extends Cpu {
public PhoneCpu(String name) {
super(name);
}
}
AbstractFactory.ComputerCpu:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class ComputerCpu extends Cpu {
public ComputerCpu(String name) {
super(name);
}
}
AbstractFactory.Screen:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public abstract class Screen {
private String name;
public Screen(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
AbstractFactory.PhoneScreen:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class PhoneScreen extends Screen {
public PhoneScreen(String name) {
super(name);
}
}
AbstractFactory.ComputerScreen:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class ComputerScreen extends Screen {
public ComputerScreen(String name) {
super(name);
}
}
AbstractFactory.AbstractComponentFactory:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class AbstractComponentFactory {
public String getCPU(){
return "Abstract CPU.";
}
public String getScreen(){
return "Abstract Screen.";
}
}
AbstractFactory.PhoneComponentFactory:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class PhoneComponentFactory extends AbstractComponentFactory{
@Override
public String getCPU(){
return "Qualcomm Snapdragon CPU: 820, 821, 845, 855 ...";
}
@Override
public String getScreen(){
return "Computer Screen: JDI LED, BOE OLED, Samsung AMOLED ...";
}
}
AbstractFactory.ComputerComponentFactory:
package AbstractFactory;
/**
* Created by chengxia on 2019/8/16.
*/
public class ComputerComponentFactory extends AbstractComponentFactory{
@Override
public String getCPU(){
return "Computer CPU: i3, i5, i7 ...";
}
@Override
public String getScreen(){
return "Computer Screen: Samsung LCD ...";
}
}
類圖如下:
下面如果有手機或者電腦廠商調(diào)用上面的類的時候钞支,可以如下:
AManufacturer.Xiaomi:
package AManufacturer;
import AbstractFactory.AbstractComponentFactory;
import AbstractFactory.PhoneComponentFactory;
/**
* Created by chengxia on 2019/8/19.
*/
public class Xiaomi {
public static void main(String []args){
AbstractComponentFactory ab = new PhoneComponentFactory();
System.out.println(ab.getCPU());
System.out.println(ab.getScreen());
}
}
AManufacturer.Lenovo:
package AManufacturer;
import AbstractFactory.AbstractComponentFactory;
import AbstractFactory.ComputerComponentFactory;
/**
* Created by chengxia on 2019/8/19.
*/
public class Lenovo {
public static void main(String []args){
AbstractComponentFactory ab = new ComputerComponentFactory();
System.out.println(ab.getCPU());
System.out.println(ab.getScreen());
}
}
Builder模式
用途
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
From: Design Patterns: Elements of Reusable Object-Oriented Software
將復雜對象的創(chuàng)建,從其表示中剝離操刀。同樣的創(chuàng)建過程烁挟,會創(chuàng)建出不同的對象。
例子
一個RTF富文本閱讀器骨坑,應該能夠?qū)⒏晃谋靖袷睫D(zhuǎn)化為各種文本格式撼嗓,如ASCII、純文本等欢唾。所以且警,目標格式多種多樣,而且不確定礁遣。所以振湾,設計上,應該讓添加一種格式轉(zhuǎn)化非常容易亡脸,最好不用修改reader本身的代碼押搪。
一種解決辦法是,讓RTFReader類調(diào)用TextConverter對象(該對象負責將RTF轉(zhuǎn)化為另一種文本表示)浅碾。每當RTFReader認出一個RTF標記符時大州,它向TextConverter發(fā)起調(diào)用來轉(zhuǎn)化這個標記。TextConverter對象既負責格式轉(zhuǎn)化垂谢,也負責將標記表示成特定的格式厦画。各種具體格式的轉(zhuǎn)化和表示,通過TextConverter的子類來實現(xiàn)滥朱。如下圖:
從上圖可以看出根暑,每一個Converter子類,實現(xiàn)了具體的復雜對象的創(chuàng)建和組裝徙邻,將這些復雜的邏輯隱藏在了抽象接口之后排嫌,和調(diào)用方reader隔離開來。這樣缰犁,reader只負責解析RTF文檔就可以了淳地。
這里每一個converter類稱為builder,而reader稱為director帅容。這個例子中颇象,Builder模式實現(xiàn)了RTF解析算法和RTF表示格式創(chuàng)建的分離。這讓我們只需要配置不同的TextConverter子類就能夠復用RTFReader的解析邏輯并徘。
說明
Builder模式適用于:
- the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled.(創(chuàng)建復雜對象的算法應該和對象組裝表示的過程分離遣钳。)
- the construction process must allow different representations for the object that's constructed.(創(chuàng)建過程必須兼容對象的不同的表示形式,也就是不同的子類麦乞。)
拋除具體場景之后蕴茴,示意圖如下:
進一步說明
上面關(guān)于Builder模式的說明是來自《Design Patterns: Elements of Reusable Object-Oriented Software》。里面提到的例子不是特別好理解路幸〖隹可以參考維基百科的說明。
Builder模式主要解決如下問題:
- How can a class (the same construction process) create different representations of a complex object?(一個類如何創(chuàng)建不同表示的復雜對象简肴。)
- How can a class that includes creating a complex object be simplified?(如何讓一個創(chuàng)建復雜對象的類變得簡單晃听?)
直接在類內(nèi)部創(chuàng)建和組裝一個復雜對象是非常不靈活的,這樣會導致類內(nèi)部有大量創(chuàng)建復雜對象表示的代碼砰识,不修改類無法修改對象的表示能扒。
Builder模式如下解決這個問題:
- Encapsulate creating and assembling the parts of a complex object in a separate object.(將復雜對象的創(chuàng)建和組裝封裝在一個單獨的對象中)
- A class delegates object creation to a Builder object instead of creating the objects directly.(一個類將對象創(chuàng)建的過程委托給一個單獨的Builder對象,而不是自己直接創(chuàng)建辫狼。)
Builder模式的初衷就是將復雜對象的創(chuàng)建和表示分離開來初斑,通過這樣,可以用同一個創(chuàng)建過程創(chuàng)建成不同表示的對象膨处。(The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.)
Builder模式的優(yōu)勢:
- Allows you to vary a product’s internal representation.(允許你創(chuàng)建一個產(chǎn)品的不同內(nèi)部表示)
- Encapsulates code for construction and representation.(實現(xiàn)將創(chuàng)建對象和表示對象的代碼封裝)
- Provides control over steps of construction process.(實現(xiàn)對象構(gòu)件過程的逐步控制)
Builder模式的劣勢:
- Requires creating a separate ConcreteBuilder for each different type of product.(對于每一種不同的對象见秤,都需要一個單獨的Builder砂竖。)
- Requires the builder classes to be mutable.(需要Builder類是可變的)。
- Data members of class aren't guaranteed to be initialized.(類的數(shù)據(jù)成員不保證都被初始化)
- Dependency injection may be less supported.(對于依賴注入的支持不好鹃答。)
維基百科給出了如下例子:
WikiBuilder.Car:
package WikiBuilder;
/**
* Created by chengxia on 2019/8/21.
*/
public class Car {
int wheel;
String color;
public Car(int wheel, String color) {
this.wheel=wheel;
this.color=color;
}
@Override
public String toString() {
return "Car [wheels = " + wheel+ ", color = " + color + "]";
}
}
WikiBuilder.CarBuilder:
package WikiBuilder;
/**
* The builder abstraction.
* */
public class CarBuilder {
int wheel;
String color;
public CarBuilder setWheel(int wheel){
this.wheel=wheel;
return this;
}
public CarBuilder setColor(String color) {
this.color=color;
return this;
}
public Car getCar() {
return new Car(wheel,color);
}
}
WikiBuilder.Client:
package WikiBuilder;
/**
* Created by chengxia on 2019/8/21.
*/
public class Client {
public static void main(String[] arg) {
Car c= new CarBuilder().setWheel(4).setColor("Black").getCar();
System.out.println(c);
}
}
運行上面的測試類乎澄,輸出如下:
Car [wheels = 4, color = Black]
Process finished with exit code 0
Builder模式和靜態(tài)內(nèi)部類
builder模式的作用將一個復雜對象的構(gòu)建與他的表示分離,使用者可以一步一步的構(gòu)建一個比較復雜的對象测摔。因此置济,在好多時候,我們可以通過創(chuàng)建一個靜態(tài)內(nèi)部類(區(qū)別于一般的內(nèi)部類锋八,靜態(tài)內(nèi)部類屬于所嵌入的外部類浙于,而不是所嵌入外部類的對象,可以直接通過外部類訪問實例化挟纱,不需要依賴于外部對象)羞酗,來實現(xiàn)Builder模式。如下:
StaticInnerBuilder.Product:
package StaticInnerBuilder;
/**
* Created by chengxia on 2019/8/22.
*/
public class Product {
private final int id;
private final String name;
private final int type;
private final float price;
private Product(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.type = builder.type;
this.price = builder.price;
}
@Override
public String toString(){
return "Builder{id:"+id+", name:"+name+", type:"+type+", price:"+price+"}";
}
public static class Builder {
private int id;
private String name;
private int type;
private float price;
public Builder id(int id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder type(int type) {
this.type = type;
return this;
}
public Builder price(float price) {
this.price = price;
return this;
}
public Product build() {
return new Product(this);
}
}
}
StaticInnerBuilder.Test:
package StaticInnerBuilder;
/**
* Created by chengxia on 2019/8/22.
*/
public class Test {
public static void main(String []args){
Product p = new Product.Builder().id(1).name("Test").type(3).price(3.14f).build();
System.out.println(p);
}
}
從這個例子中樊销,可以看出整慎,這樣寫的時候,可以在構(gòu)造對象的時候采用鏈式寫法围苫,比較有意思裤园。運行前面的測試類輸出如下:
Builder{id:1, name:Test, type:3, price:3.14}
Process finished with exit code 0
工廠方法模式
用途
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
From: Design Patterns: Elements of Reusable Object-Oriented Software
定義一個創(chuàng)建對象的接口,但是讓子類去決定實例化哪個具體類剂府。工廠方法讓一個類將實例化的過程下放到子類中拧揽。
例子
以一個能夠開發(fā)給用戶展示多種Document的應用框架為例。這個框架的兩個重要的類是Application和Document腺占,這兩個類都是抽象的淤袜,使用時必須要根據(jù)具體的應用場景做具體的實現(xiàn)。當我們開發(fā)一個繪圖應用時衰伯,我們要自己實現(xiàn)DrawingApplication和DrawingDocument铡羡。這個Application類負責管理Document,比如當用戶點擊菜單新建時意鲸,需要創(chuàng)建一個新的Document類烦周。
由于具體的要實例化哪個Application子類,是由具體要實現(xiàn)的應用確定的怎顾。Application類并不知道要實例化哪個Document子類读慎,他只知道什么時候創(chuàng)建,僅此而已槐雾。這就進退兩難了:框架需要實例化類夭委,但是它只知道抽象類,但是這個類不能被實例化募强。
這時候株灸,工廠方法提供了一種解決方案:它對創(chuàng)建哪個Document類的子類做了封裝崇摄,然后將這一過程移到框架外。(It encapsulates the knowledge of which Document subclass to create and moves this knowledge out of the framework.)如下圖蚂且。
Application subclasses redefine an abstract CreateDocument operation on Application to return the appropriate Document subclass. Once an Application subclass is instantiated, it can then instantiate application-specific Documents
without knowing their class. We call CreateDocument a factory method because it's
responsible for "manufacturing" an object.
Application類的子類重新寫了一個Application的CreateDocument方法配猫,用來返回一個合適的Document子類。一旦這個Application類的子類實例化完成杏死,它就能夠?qū)嵗虾踹@個Application子類的Document類,而不用知道具體的它們(Document類)具體的類捆交。
我們之所以叫CreateDocument工廠方法淑翼,是因為它負責“生產(chǎn)”對象。
說明
工廠方法模式適用于:
- 一個類不能預測它將要創(chuàng)建的類對象時品追。
- 一個類想讓它的子類確定具體實例化哪個類玄括。
- classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.(這句話翻不了,直接拿過來吧)
拋除具體場景后肉瓦,類圖如下:
工廠方法使得無需在代碼中綁定具體應用的類遭京,在代碼中,只需和Product接口打交道泞莉。因此哪雕,能夠適用于任何用戶自定義的具體ConcreteProduct類。
工廠方法一個不好的地方在于鲫趁,用戶必須要創(chuàng)建一個Creator類的子類斯嚎,來創(chuàng)建一個具體的ConcreteProduct對象。如果本來就有這樣的繼承層次還好挨厚,否則有些麻煩堡僻。
前面的例子中,工廠方法只通過Creator調(diào)用疫剃。但是钉疫,有時候,會出現(xiàn)類的平行繼承關(guān)系巢价。如下:
在這個例子中牲阁,通過工廠方法,實際上連接了兩個平行的繼承層次:Figure和Manipulator蹄溉。工廠方法定義了兩個類層次之間的聯(lián)系咨油,通過它,實際上實現(xiàn)了類之間聯(lián)系的內(nèi)聚柒爵。
例子
代碼如下:
FactoryMethod.Shape:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
public abstract class Shape {
public String name;
public Shape(String aName){
name = aName;
}
//畫shape
public abstract void draw();
//擦去shape
public abstract void erase();
}
FactoryMethod.Square:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
//方形子類
public class Square extends Shape {
//構(gòu)造函數(shù)
public Square(String aName){
super(aName);
}
public void draw() {
System.out.println("It will draw a Square.");
}
public void erase() {
System.out.println("It will erase a Square.");
}
}
FactoryMethod.Circle:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
// 圓形子類
public class Circle extends Shape {
// 構(gòu)造函數(shù)
public Circle(String aName){
super(aName);
}
public void draw() {
System.out.println("It will draw a Circle.");
}
public void erase() {
System.out.println("It will erase a Circle.");
}
}
FactoryMethod.ShapeFactory:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
public abstract class ShapeFactory {
protected abstract Shape factoryMethod(String aName);
//在operOnShape中定義對Shape的一系列使用操作
public void operOnShape(String aName){
Shape s = factoryMethod(aName);
System.out.println("The current using shape is: " + s.name);
s.draw();
s.erase();
}
}
FactoryMethod.SquareFactory:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
//定義返回Square實例的SquareFactory
public class SquareFactory extends ShapeFactory {
//重載factoryMethod方法,返回Circle對象
protected Shape factoryMethod(String aName) {
return new Square(aName + " (created by SquareFactory)");
}
}
FactoryMethod.CircleFactory:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
//定義返回Circle 實例的CircleFactory
public class CircleFactory extends ShapeFactory {
//重載factoryMethod方法,返回Circle對象
protected Shape factoryMethod(String aName) {
return new Circle(aName + " (created by CircleFactory)");
}
}
類圖如下:
[圖片上傳失敗...(image-7f4400-1566435759539)]
接下來寫一個測試類役电,來調(diào)動上面的程序結(jié)構(gòu),如下棉胀。
FactoryMethod.TestFactoryMethod:
package FactoryMethod;
/**
* Created by chengxia on 2019/8/19.
*/
public class TestFactoryMethod {
public static void main(String[] args){
ShapeFactory sf1 = new SquareFactory();
ShapeFactory sf2 = new CircleFactory();
sf1.operOnShape("Shape one");
sf2.operOnShape("Shape two");
}
}
從這個例子可以看出法瑟,ShapeFactory實際上將實例化Shape的職能下放到了子類SquareFactory和CircleFactory冀膝。這樣,它就只需要關(guān)注具體Shape實例的行為霎挟,關(guān)注對于Shape實例的操作窝剖,而無需關(guān)注該實例是如何實例化的。
運行上面的測試例子酥夭,結(jié)果如下:
The current using shape is: Shape one (created by SquareFactory)
It will draw a Square.
It will erase a Square.
The current using shape is: Shape two (created by CircleFactory)
It will draw a Circle.
It will erase a Circle.
Process finished with exit code 0
參考資料
- Design Patterns: Elements of Reusable Object-Oriented Software
- 在 Java 中應用設計模式 - Factory Method
- Builder pattern - Wikipedia
- Java設計模式之builder模式