創建型模式是處理對象創建的設計模式,試圖根據實際情況使用合適的方式創建對象。基本的對象創建方式可能會導致設計上的問題,或增加設計的複雜度。創建型模式通過以某種方式控制對象的創建來解決問題。創建型模式由兩個主導思想構成。一是將系統使用的具體類封裝起來,二是隱藏這些具體類的實例創建和結合的方式。 ...
創建型設計模式
爭對
對象/類
創建時的優化
工廠方法模式(瞭解)
通過定義頂層抽象工廠類,通過繼承的方式,針對於每一個產品都提供一個工廠類用於創建。
情況:只適用於簡單對象,當我們需要生產許多個產品族的時候,這種模式就有點乏力了
創建對象不再使用傳統的new,而是創建一個工廠類,作為all實體類創建對象的一個封裝類。(避免了更改類名、構造方法時,需要修改大量的代碼)
簡單工廠模式:(不靈活,不建議)
不符合開閉原則。如果輸入沒有提前寫好的水果則就需要再添加每個類里的代碼
//水果抽象類
public abstract class Fruit {
private final String name;
public Fruit(String name){
this.name = name;
}
@Override
public String toString() {
return name+"@"+hashCode(); //列印一下當前水果名稱,還有對象的hashCode
}
}
//水果實體類
public class Apple extends Fruit{ //蘋果,繼承自水果
public Apple() {
super("蘋果");
}
}
public class Orange extends Fruit{ //橘子,也是繼承自水果
public Orange() {
super("橘子");
}
}
//水果工廠
public class FruitFactory {
/**
* 這裡就直接來一個靜態方法根據指定類型進行創建
* @param type 水果類型
* @return 對應的水果對象
*/
public static Fruit getFruit(String type) {
switch (type) {
case "蘋果":
return new Apple();
case "橘子":
return new Orange();
default:
return null;
}
}
}
//主方法
public class Main {
public static void main(String[] args) {
Fruit fruit = FruitFactory.getFruit("橘子"); //直接問工廠要,而不是我們自己去創建
System.out.println(fruit);
}
}
工廠方法模式:通過範型靈活實現
如果新增了水果類型,直接創建一個新的工廠類就行,不需要修改之前已經編寫好的內容。
缺點:一種水果就有一種新的工廠類,太多工廠類了
//水果抽象類
public abstract class Fruit {
private final String name;
public Fruit(String name){
this.name = name;
}
@Override
public String toString() {
return name+"@"+hashCode(); //列印一下當前水果名稱,還有對象的hashCode
}
}
//水果工廠
public abstract class FruitFactory<T extends Fruit> { //將水果工廠抽象為抽象類,添加泛型T由子類指定水果類型
public abstract T getFruit(); //不同的水果工廠,通過此方法生產不同的水果
}
//Apple工廠
public class AppleFactory extends FruitFactory<Apple> { //蘋果工廠,直接返回Apple,一步到位
@Override
public Apple getFruit() {
return new Apple();
}
}
//主方法
public class Main {
public static void main(String[] args) {
test(new AppleFactory()::getFruit); //比如我們現在要吃一個蘋果,那麼就直接通過蘋果工廠來獲取蘋果
}
//此方法模擬吃掉一個水果
private static void test(Supplier<Fruit> supplier){
System.out.println(supplier.get()+" 被吃掉了,真好吃。");
}
}
抽象工廠模式
情況:適用於有一系列產品的公司。
缺點:容易違背開閉原則。一旦增加了一種產品,此時就必須去修改抽象工廠的介面,這樣就涉及到抽象工廠類的以及所有子類的改變
舉例:
實際上這些產品都是成族出現的,比如小米的產品線上有小米12,小米平板等,華為的產品線上也有華為手機、華為平板,但是如果按照我們之前工廠方法模式來進行設計,那就需要單獨設計9個工廠來生產上面這些產品,顯然這樣就比較浪費時間的。
我們就可以使用抽象工廠模式,我們可以將多個產品,都放在一個工廠中進行生成,按不同的產品族進行劃分,比如小米,那麼我就可以安排一個小米工廠,而這個工廠裡面就可以生產整條產品線上的內容,包括小米手機、小米平板、小米路由等。
//工廠抽象類
public abstract class AbstractFactory {
public abstract Phone getPhone();
public abstract Table getTable();
public abstract Router getRouter();
}
//工廠實現類
public class AbstractFactoryImpl extends AbstractFactory{
@Override
public Phone getPhone() {
return new ProductPhone();
}
@Override
public Table getTable() {
return new ProductTable();
}
@Override
public Router getRouter() {
return new ProductRouter();
}
}
//產品抽象類
public abstract class AbRouter{
public abstract Router getRouter();
}
...
//產品實體類
public class Router extends AbRouter{
@Override
public Router getRouter(){
return new Router();
}
}
建造者模式
當構造對象時參數較多,可以通過建造者模式使用鏈式方法創建對象,保證參數填寫正確。
可以去看看StringBuilder的源碼,有很多的框架都為我們提供了形如XXXBuilder
的類型,我們一般也是使用這些類來創建我們需要的對象。
建造者模式創建對象其實和StringBuilder
一樣:實際上我們是通過建造者來不斷配置參數或是內容,當我們配置完所有內容後,最後再進行對象的構建。
public static void main(String[] args) {
StringBuilder builder = new StringBuilder(); //創建一個StringBuilder來逐步構建一個字元串
builder.append(666); //拼接一個數字
builder.append("老鐵"); //拼接一個字元串
builder.insert(2, '?'); //在第三個位置插入一個字元
System.out.println(builder.toString()); //差不多成形了,最後轉換為字元串
}
舉例:
//實體類的編寫
public class Student {
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;
//一律使用建造者來創建,不對外直接開放
private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
this.id = id;
this.age = age;
this.grade = grade;
this.name = name;
this.college = college;
this.profession = profession;
this.awards = awards;
}
public static StudentBuilder builder(){ //通過builder方法直接獲取建造者
return new StudentBuilder();
}
public static class StudentBuilder{ //這裡就直接創建一個內部類
//Builder也需要將所有的參數都進行暫時保存,所以Student怎麼定義的這裡就怎麼定義
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;
public StudentBuilder id(int id){ //直接調用建造者對應的方法,為對應的屬性賦值
this.id = id;
return this; //為了支持鏈式調用,這裡直接返回建造者本身,下同
}
public StudentBuilder age(int age){
this.age = age;
return this;
}
...
public StudentBuilder awards(String... awards){
this.awards = Arrays.asList(awards);
return this;
}
public Student build(){ //最後我們只需要調用建造者提供的build方法即可根據我們的配置返回一個對象
return new Student(id, age, grade, name, college, profession, awards);
}
}
}
//主方法
public static void main(String[] args) {
Student student = Student.builder() //獲取建造者
.id(1) //逐步配置各個參數
.age(18)
.grade(3)
.name("小明")
.awards("ICPC-ACM 區域賽 金牌", "LPL 2022春季賽 冠軍")
.build(); //最後直接建造我們想要的對象
}
單例模式
單例模式:在電腦進程中,同一個類始終只會有一個對象來進行操作。
多例模式:在電腦進程中,對一個實體類創建一次對象就是對當個對象操作,若是創建多個對象則是分別對對應的對象操作。
單例模式的三種寫法:
-
餓漢式單例(不建議)
在最開始就創建了對象(太饑渴了,一開始就需要對象)
public class Singleton { private final static Singleton INSTANCE = new Singleton(); //用於引用全局唯一的單例對象,在一開始就創建好 private Singleton() {} //禁用了構造方法Singleton()來創建對象。不允許隨便new,需要對象直接找getInstance public static Singleton getInstance(){ //獲取全局唯一的單例對象 return INSTANCE; } }
-
加鎖的懶漢式單例(不建議,沒有第三種方法好)
懶漢:在要用的時候才創建對象。但又得防多線程就上了鎖
public class Singleton { private static volatile Singleton INSTANCE; //在一開始先不進行對象創建。volatile關鍵字是多線程的時候,這個變數更改了,別的線程可以立馬檢測到 private Singleton() {} //禁用了構造方法Singleton()來創建對象。不允許隨便new,需要對象直接找getInstance public static Singleton getInstance(){ if(INSTANCE == null) { //這層判斷是便於第一次外訪問時不用在走鎖 synchronized (Singleton.class) { //加鎖是為了防止多線程創建了多個對象 if(INSTANCE == null) INSTANCE = new Singleton(); //由於加了鎖,所以當一個進程進來創建了對象,其他線程需要再判斷一次有沒有人已經創建了這個類對象,有就不創建了。內層還要進行一次檢查,雙重檢查鎖定 } } return INSTANCE; } }
-
靜態內部類的半懶、半餓式單例(建議)
靜態內部類該開始不會載入,需要的時候才會載入,由於這個類一載入就會創建對象。
所以實現了懶漢的資源不濫用,餓漢的防止多線程
public class Singleton { private Singleton() {}//禁用了構造方法Singleton()來創建對象。不允許隨便new,需要對象直接找getInstance private static class Holder { //由靜態內部類持有單例對象,但是根據類載入特性,我們僅使用Singleton類時,不會對靜態內部類進行初始化。一旦類初始化之後值將不會改變,有點餓漢式的味道。 private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ //只有真正使用內部類時,才會進行類初始化 return Holder.INSTANCE; //直接獲取內部類中的 } }
原型模式
定義:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。(說白了就是複製)
- 淺拷貝:①對於類中基本數據類型,會直接複製值給拷貝對象;②對於引用類型(對象類型),只會複製對象的地址,而實際上指向的還是原來的那個對象,拷貝個基莫。
public static void main(String[] args) {
int a = 10;
int b = a; //基本類型淺拷貝
System.out.println(a == b); //true
Object o = new Object();
Object k = o; //引用類型淺拷貝,拷貝的僅僅是對上面對象的引用
System.out.println(o == k); //true
}
- 深拷貝:無論是基本類型還是引用類型,深拷貝會將引用類型的所有內容,全部拷貝為一個新的對象,包括對象內部的所有成員變數,也會進行拷貝。
使用Cloneable介面提供的拷貝機制,來實現原型模式:操作完會發現Object的clone
預設還是淺複製
protected class Student implements Cloneable{ //註意需要實現Cloneable介面
...
//Cloneable中的方法,下麵代碼複製Object的clone源碼
@Override
public Object clone() throws CloneNotSupportedException { //提升clone方法的訪問許可權
return super.clone();
}
}
//主方法
public static void main(String[] args) throws CloneNotSupportedException {
Student student0 = new Student();
Student student1 = (Student) student0.clone();
System.out.println(student0);
System.out.println(student1);
//兩個結果不同,就是地址不同
Student student0 = new Student("小明");
Student student1 = (Student) student0.clone();
System.out.println(student0.getName() == student1.getName());
//true
}
深拷貝:在student實現介面Cloneable後重寫clone方法
@Override
public Object clone() throws CloneNotSupportedException { //這裡我們改進一下,針對成員變數也進行拷貝
Student student = (Student) super.clone();
student.name = new String(name);
return student; //成員拷貝完成後,再返回
}