在軟件工程中,創建型模式是處理對象創建的設計模式,試圖根據實際情況使用合適的方式創建對象。基本的對象創建方式可能會導致設計上的問題,或增加設計的復雜度。創建型模式通過以某種方式控制對象的創建來解決問題。
常用創建型模式有:單例模式、工廠模式、抽象工廠模式、原型模式、建造者模式
一、單例模式
單例模式有以下8種寫法:
- 餓漢式:
- 靜態常量
- 靜態代碼塊
- 懶漢式:
- 線程不安全
- 線程安全,同步方法
- 線程安全,同步代碼塊
- 雙重檢查
- 靜態內部類
- 枚舉
單例模式的使用場景:
需要頻繁創建和銷毀的對象;創建時耗時過多或消耗資源過多,但又經常用到的對象(比如session工廠、數據源等)
1. 餓漢式 - 靜態常量寫法
代碼實現:
/**
* 設計模式之單例模式
* 餓漢式(靜態常量)
*/
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("兩次獲取的實例一樣嗎:" + (instance1 == instance2)); //true
}
}
class Singleton {
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton() {
}
//定義為常量,保證實例對象不變
private final static Singleton instance = new Singleton();
//通過此方法獲取實例
public static Singleton getInstance() {
return instance;
}
}
分析:
優點:
- 使用方式簡單,在類加載的時候創建實例對象,避免了線程同步問題
缺點:
- 在類加載的時候創建實例對象,但不確定何時使用、是否使用,可能造成內存浪費
2. 餓漢式 - 靜態代碼塊寫法
代碼實現:
/**
* 設計模式之單例模式
* 餓漢式(靜態代碼塊寫法)
*/
class Singleton{
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton(){
}
//定義為常量,保證實例對象不變
private final static Singleton instance;
static {
instance = new Singleton();
}
//通過此方法獲取實例
public static Singleton getInstance(){
return instance;
}
}
分析:
和靜態常量一致,只不過初始化的位置不同,一個在靜態代碼塊,一個直接在常量聲明處初始化
3. 懶漢式 - 線程不安全
代碼實現:
/**
* 設計模式之單例模式
* 懶漢式(線程不安全)
*/
class Singleton {
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton() {
}
//聲明實例對象
private static Singleton instance;
//通過此方法獲取實例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析:
優點:
- 滿足隨用隨拿的特點,解決了內存浪費的問題
缺點:
- 線程不安全,當多個線程訪問時,可能創建多個實例,因此實際開發中不可使用
4. 懶漢式 - 線程安全 - 同步方法寫法
代碼實現:
/**
* 設計模式之單例模式
* 懶漢式(同步方法)
*/
class Singleton {
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton() {
}
//聲明實例對象
private static Singleton instance;
//通過此方法獲取實例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析:
雖然解決了線程不安全問題,但鎖的范圍太大,效率低,開發中盡量不要使用
5. 懶漢式 - 線程安全 - 同步代碼塊寫法
代碼實現:
/**
* 設計模式之單例模式
* 懶漢式(同步代碼塊寫法)
*/
class Singleton {
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton() {
}
//聲明實例對象
private static Singleton instance;
//通過此方法獲取實例
public static Singleton getInstance() {
if (instance == null) {
//同步代碼
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
分析:
這種方式將同步鎖縮小了范圍,本意是解決效率問題,但又造成了線程不安全,因此開發中不可使用
6. 懶漢式 - 雙重檢查(推薦使用)
代碼實現:
/**
* 設計模式之單例模式
* 雙重檢查
*/
class Singleton {
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton() {
}
//聲明實例對象
private static volatile Singleton instance;
//雙重判斷 + 同步鎖
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
分析:
既提高了效率也解決了線程安全問題,推薦使用這種方法
7. 懶漢式 - 靜態內部類(推薦使用)
代碼實現:
/**
* 設計模式之單例模式
* 靜態內部類
*/
class Singleton {
//私有構造方法,使其不可在外部通過構造器實例化
private Singleton() {
}
//靜態內部類
private static class SingletonInstance{
private final static Singleton INSTANCE = new Singleton();
}
//獲取實例
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
分析:
利用了類加載機制,保證初始化實例時只有一個線程。Singleton類被裝載時並不會被實例化,當調用getInstance方法時才會裝載SingletonInstance
8. 懶漢式 - 枚舉法(推薦使用)
代碼實現:
/**
* 設計模式之單例模式
* 枚舉
*/
enum Singleton{
INSTANCE;
}
分析:
不僅能規避線程不安全,還能防止反序列化重新創建新的對象
二、工廠模式
1. 簡單工廠模式
1.1 介紹
嚴格來說,簡單工廠模式並不是23種常見的設計模式之一,它只算工廠模式的一個特殊實現。簡單工廠模式在實際中的應用相對於其他2個工廠模式用的還是相對少得多,因為它只適應很多簡單的情況。
簡單工廠模式違背了 開閉原則 (但可以通過反射的機制來避免) 。因為每次你要新添加一個功能,都需要在生switch-case 語句(或者if-else 語句)中去修改代碼,添加分支條件。
1.2 適用場景
- 需要創建的對象較少
- 客戶端不關心對象的創建過程
1.3 簡單工廠模式角色分配
- 工廠(Factory)角色 :簡單工廠模式的核心,它負責實現創建所有實例的內部邏輯。工廠類可以被外界直接調用,創建所需的產品對象。
- 抽象產品(Product)角色 :簡單工廠模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口。
- 具體產品(Concrete Product)角色:簡單工廠模式的創建目標,所有創建的對象都是充當這個角色的某個具體類的實例。
1.4 簡單工廠模式代碼實現
新建一個抽象產品 Shape
public interface Shape {
void draw();
}
具體產品 Circle、Square,實現 Shape 接口
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("圓形");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("正方形");
}
}
工廠 ShapeFactory
public class ShapeFactory {
public static Shape getShape(String name){
if(name == null){
return null;
}
if ("SQUARE".equalsIgnoreCase(name)){
return new Square();
}else if("CIRCLE".equalsIgnoreCase(name)){
return new Circle();
}else{
return null;
}
}
}
客戶端 Client
public class Client {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("circle");
circle.draw();
Shape square = ShapeFactory.getShape("square");
square.draw();
}
}
運行結果
圓形
正方形
雖然實現了簡單工廠模式,但是當我們新增一個需求的時候,需要修改ShapeFactory類的代碼,違反了開閉原則,我們可以用反射的方式重寫工廠方法
public class ShapeFactory2 {
public static Object getClass(Class<? extends Shape> clazz){
if (clazz == null){
return null;
}
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
測試
public class Client2 {
public static void main(String[] args) {
Circle circle = (Circle) ShapeFactory2.getClass(Circle.class);
circle.draw();
Square square = (Square) ShapeFactory2.getClass(Square.class);
square.draw();
}
}
運行結果
圓形
正方形
2. 工廠方法模式
2.1 介紹
工廠方法模式應該是在工廠模式家族中是用的最多模式,一般項目中存在最多的就是這個模式。
工廠方法模式是簡單工廠的僅一步深化, 在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的對象,而是針對不同的對象提供不同的工廠。也就是說 每個對象都有一個與之對應的工廠 。
2.2 適用場景
-
一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
-
一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏
-
將創建對象的任務委托給多個工廠子類中的某一個,客戶端在使用時可以無需關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。
2.3 工廠方法模式角色分配
- 抽象工廠(Abstract Factory)角色:是工廠方法模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。
- 具體工廠(Concrete Factory)角色 :這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建某一種產品對象。
- 抽象產品(Abstract Product)角色 :工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
- 具體產品(Concrete Product)角色 :這個角色實現了抽象產品角色所定義的接口。某具體產品有專門的具體工廠創建,它們之間往往一一對應
2.4 工廠方法模式代碼實現
抽象工廠 Factory
public interface Factory {
Shape getShape();
}
具體工廠 CircleFactory、SquareFactory
public class CircleFactory implements Factory {
@Override
public Shape getShape() {
return new Circle();
}
}
public class SquareFactory implements Factory {
@Override
public Shape getShape() {
return new Square();
}
}
抽象產品和具體產品繼續使用簡單工廠模式中的類
客戶端
public class Client {
public static void main(String[] args) {
Shape circle = new CircleFactory().getShape();
circle.draw();
Shape square = new SquareFactory().getShape();
square.draw();
}
}
運行結果
圓形
正方形
3. 抽象工廠模式
3.1 介紹
在工廠方法模式中,其實我們有一個潛在意識的意識。那就是我們生產的都是同一類產品。抽象工廠模式是工廠方法的僅一步深化,在這個模式中的工廠類不單單可以創建一種產品,而是可以創建一組產品。
將工廠抽象成兩層,Abstract Factory(抽象工廠)和具體實現的工廠子類。程序員可以根據創建對象類型使用對應的工廠子類。這樣將單個的簡單工廠類變成了工廠簇,更利於代碼的維護和擴展。
3.2 適用場景
-
和工廠方法一樣客戶端不需要知道它所創建的對象的類。
-
需要一組對象共同完成某種功能時,並且可能存在多組對象完成不同功能的情況。(同屬於同一個產品族的產品)
-
系統結構穩定,不會頻繁的增加對象。(因為一旦增加就需要修改原有代碼,不符合開閉原則)
3.3 抽象工廠模式角色分配
- 抽象工廠(Abstract Factory)角色 :是工廠方法模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。
- 具體工廠類(Concrete Factory)角色 :這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建某一種產品對象。
- 抽象產品(Abstract Product)角色 :工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
- 具體產品(Concrete Product)角色 :抽象工廠模式所創建的任何產品對象都是某一個具體產品類的實例。在抽象工廠中創建的產品屬於同一產品族,這不同於工廠模式中的工廠只創建單一產品。
3.4 抽象工廠的工廠和工廠方法中的工廠有什么區別?
抽象工廠是生產一整套有產品的(至少要生產兩個產品),這些產品必須相互是有關系或有依賴的,而工廠方法中的工廠是生產單一產品的工廠。
3.5 抽象工廠模式代碼實現
抽象產品:Gun、Bullet
public interface Gun {
void shooting();
}
public interface Bullet {
void loading();
}
具體產品:AK47、AK47Bullet、M16、M16Bullet
public class AK47 implements Gun {
@Override
public void shooting() {
System.out.println("AK47射擊");
}
}
public class AK47Bullet implements Bullet {
@Override
public void loading() {
System.out.println("AK47裝子彈");
}
}
public class M16 implements Gun {
@Override
public void shooting() {
System.out.println("M16射擊");
}
}
public class M16Bullet implements Bullet {
@Override
public void loading() {
System.out.println("M16裝子彈");
}
}
抽象工廠:ArmsFactory
public interface ArmsFactory {
Gun produceGun();
Bullet produceBullet();
}
具體工廠:
public class AK47Factory implements ArmsFactory{
@Override
public Gun produceGun() {
return new AK47();
}
@Override
public Bullet produceBullet() {
return new AK47Bullet();
}
}
public class M16Factory implements ArmsFactory{
@Override
public Gun produceGun() {
return new M16();
}
@Override
public Bullet produceBullet() {
return new M16Bullet();
}
}
測試
public class Client {
public static void main(String[] args) {
ArmsFactory factory;
factory = new AK47Factory();
Gun ak47 = factory.produceGun();
Bullet ak47Bullet = factory.produceBullet();
ak47Bullet.loading();
ak47.shooting();
factory = new M16Factory();
Gun m16 = factory.produceGun();
Bullet m16Bullet = factory.produceBullet();
m16Bullet.loading();
m16.shooting();
}
}
結果
AK47裝子彈
AK47射擊
M16裝子彈
M16射擊
參考:深入理解工廠模式
三、原型模式
原型模式是指:用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象。
原型模式是一種創建型設計模式,允許一個對象再創建另外一個可定制的對象,無需知道如何創建的細節
可以通過重寫clone方法實現拷貝,拷貝又分為淺拷貝和深拷貝
1. 淺拷貝:
-
對於基本數據類型的成員變量,淺拷貝會直接進行值傳遞,將該屬性值復制一份給新的對象
-
對於引用類型的成員變量,淺拷貝知識將該成員變量的引用值(內存地址)復制一份給新的對象,因此會造成一個對象修改值影響另外一個對象
-
淺拷貝時使用默認的clone()方法來實現,例如:
Person = (Person)super.clone()
2. 深拷貝:
- 復制對象的所有基本數據類型的成員變量值
- 為所有引用類型的成員變量申請存儲空間,並復制每個引用數據類型成員變量所引用的對象
- 可通過重寫clone方法和對象序列化方式(推薦)實現
//使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Company company = null;
try {
company = (Company) super.clone();
//處理引用類型
company.employee = (Employee) employee.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
//使用序列化
protected Object deepClone(){
ByteArrayInputStream bis = null;
ByteArrayOutputStream bos = null;
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return ois.readObject();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
3. 原型模式的分析:
- 創建新的對象比較復雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率
- 不用重新初始化對象,而是動態的獲取對象運行時的狀態
- 如果原始對象發生變化(增加或減少屬性),其它克隆對象也會發生變化,無需修改代碼
- 淺拷貝和深拷貝對引用類型的處理方式不一樣
四、建造者模式
1. 基本介紹
- 建造者模式(Builder Pattern)又叫生成器模式,是一種對象構建模式。它可以將復雜對象的建造過程抽象出來(抽象類別),使這個抽象過程的不同實現方法可以構造出不同表現(屬性)的對象。
- 建造者模式是一步步創建一個復雜的對象,它允許用戶只通過指定復雜對象的類型和內容就可以構造它們,用戶不需要知道內部的具體構建細節。
2. 建造者模式的四個角色
- 產品角色(Product):一個具體的產品對象
- 抽象建造者(Builder):創建一個Product對象的各個部件指定的接口或抽象類
- 具體建造者(Concrete Builder):實現接口,構建和裝配各個部件。
- 指揮者(Director):構建一個使用Builder接口的對象。它主要是用於創建一個復雜的對象。它主要有兩個作用:① 隔離了客戶與對象的生產過程,②負責控制產品對象的生產過程。
3. 原理類圖:
4. 建造者模式在 JDK - StringBuilder 中的應用
在 StringBuilder 繼承關系中:
StringBuilder 繼承了 AbstractStringBuilder ,AbstractStringBuilder 實現了 Appendable 接口
角色分析:
- 抽象建造者:Appendable
- 具體建造者:AbstractStringBuilder
- 指揮者:StringBuilder
5. 建造者模式注意事項與細節
- 客戶端(使用程序)不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象
- 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象
- 可以更加精細地控制產品的創建過程。將復雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程
- 增加新的具體建造者無須修改原有類庫的代碼,指揮者類針對抽象建造者類編程,系統擴展方便,符合“開閉原則”
6. 建造者模式代碼實現
具體產品:House
public class House {
private String basic;
private String wall;
private String roofed;
//省略getter setter toString()方法
}
抽象建造者:HouseBuilder
public abstract class HouseBuilder {
protected House house = new House();
public abstract void buildBasic();
public abstract void buildWall();
public abstract void buildRoof();
public House buildHouse() {
return house;
}
}
具體建造者:CommonHouse、HighHouse
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("打10m的地基");
}
@Override
public void buildWall() {
System.out.println("砌20cm的牆");
}
@Override
public void buildRoof() {
System.out.println("封瓦片頂");
}
}
public class HighHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("打20m的地基");
}
@Override
public void buildWall() {
System.out.println("砌50cm的牆");
}
@Override
public void buildRoof() {
System.out.println("封玻璃頂");
}
}
指揮者:HouseDirector
public class HouseDirector {
private HouseBuilder builder;
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
public House build(){
builder.buildBasic();
builder.buildWall();
builder.buildRoof();
return builder.buildHouse();
}
}
測試:
public class Client {
public static void main(String[] args) {
HouseDirector builderDirector1 = new HouseDirector(new CommonHouse());
builderDirector1.build();
System.out.println("---------");
HouseDirector builderDirector2 = new HouseDirector(new HighHouse());
builderDirector2.build();
}
}
結果:
打10m的地基
砌20cm的牆
封瓦片頂
---------
打20m的地基
砌50cm的牆
封玻璃頂
p.s. 所有代碼和筆記均可在 我的GitHub 中獲取,如果對您有幫助的話,可以點個 star 支持一下 🙈