一、創建型模式(Factory Method)
1.工廠模式
1.1普通工廠模式
就是建立一個工廠類,對實現了同一接口的一些類進行實例的創建。首先看下關系圖:
舉例如下:(我們舉一個發送郵件和短信的例子)
首先創建二者的共同接口
public interface Sender {
public void Send();
}
其次,創建實現類:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
最后,建工廠類:
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("請輸入正確的類型!");
return null;
}
}
}
我們來測試下:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
1.2 多工廠方法模式
是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。關系圖:
將上面的代碼做下修改,改動下SendFactory類就行,如下:
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
測試類如下:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
輸出:this is mailsender!
1.3 靜態工廠方法模式
將上面的多個工廠方法模式里的方法置為靜態的,不需要創建實例,直接調用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
輸出:this is mailsender!
總體來說,工廠模式適合:凡是出現了大量的產品需要創建,並且具有共同的接口時,可以通過工廠方法模式進行創建。在以上的三種模式中,第一種如果傳入的字符串有誤,不能正確創建對象,第三種相對於第二種,不需要實例化工廠類,所以,大多數情況下,我們會選用第三種——靜態工廠方法模式。
2. 抽象工廠模式
工廠方法模式有一個問題就是,類的創建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?就用到抽象工廠模式,創建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。因為抽象工廠不太好理解,我們先看看圖,然后就和代碼,就比較容易理解。
請看例子:
public interface Sender {
public void Send();
}
兩個實現類:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
兩個工廠類:
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
在提供一個接口:
public interface Provider {
public Sender produce();
}
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
其實這個模式的好處就是,如果你現在想增加一個功能:發及時信息,則只需做一個實現類,實現Sender接口,同時做一個工廠類,實現Provider接口,就OK了,無需去改動現成的代碼。這樣做,拓展性較好!
3. 單例模式
通過私有構造方法,不允許外面去創建對象,實現只有一個實例。
3.1 餓漢式單例
缺點:一開始就把所有的對象都加載,容易浪費空間。
public class Hungry {
//核心是構造,不允許外面去創建對象
private Hungry() {
}
//創建靜態變量
private final static Hungry HUNGRY = new Hungry();
//返回對象
public static Hungry getInstance() {
return HUNGRY;
}
}
3.2 懶漢式單例
(1)只有在對象為空時才去new對象------單線程下沒有問題
public class LazyMan {
private LazyMan() {
}
private static LazyMan lazyMan;
//只有在對象為空時才去new對象------單線程下沒有問題
public static LazyMan getInstance() {
if(lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
(2)通過多線程去測試,發現單例被破壞。
private LazyMan() {
System.out.println(Thread.currentThread().getName()+"ok");
}
//多線程的情況下就會出現多個對象
public static void main(String[] args) {
for(int i=0;i<10;i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
去實現雙重檢測鎖模式的懶漢式單例--DCL懶漢式,並且在lazyMan = new LazyMan();位置,由於還不是原子操作,容易造成指令重排,去加上volatile屬性,他是可見的不是原子的,來保證安全。
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if(lazyMan==null) {
synchronized (LazyMan.class) { //鎖住類就會只有一個對象
if(lazyMan == null) {
lazyMan = new LazyMan(); //不是一個原子操作
/**
* 1. 分配內存空間
* 2. 執行構造方法,初始化對象
* 3. 把對象指向這個空間
* 假如又A,B兩個線程,A執行了132是沒有問題的,但是當B在執行時就 直接return了,但是此時還沒有完成構造,是不安全的。
* volatile只能保證可見性,不證原子性。
* */
}
}
}
return lazyMan;
}
(3)通過反射機制去破環構造方法的私有性,從而破壞單例模式---一個反射對象。
public static void main(String[] args) throws Exception{
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //用反射使其私有構造無效
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
去實現三重檢測,也就是在構造方法上再次進行加鎖判斷,避免某一種反射的破壞,其余不變。
private LazyMan() {
synchronized (LazyMan.class) {
if(lazyMan!=null) {
throw new RuntimeException("不要試圖使用反射破壞單例");
}
}
}
(4)上面的先正常創建對象,再用反射,如果我們直接建立多個反射對象就可以破壞單例。
public static void main(String[] args) throws Exception{
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //用反射使其私有構造無效
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
通過增加一個標志位,可以防止多個反射對象的破壞。
private static boolean flag = false;
private LazyMan() {
synchronized (LazyMan.class) {
if(flag==false) {
flag = true;
}else {
throw new RuntimeException("不要試圖使用反射破壞單例");
}
}
}
(5)如果我們能夠破解這個標志位,仍然是可以去破壞單例。
public static void main(String[] args) throws Exception{
Field flag = LazyMan.class.getDeclaredField("flag");//獲得標志位
flag.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //用反射使其私有構造無效
LazyMan instance = declaredConstructor.newInstance();
flag.set(instance, false); //破壞標志位
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
(6)通過使用枚舉去保證單例和安全,因為枚舉是不可以被反射的。
public enum EnumSingle {
INSTANCE;
//枚舉本身也是一個類,只是繼承了枚舉
public EnumSingle getInstance() {
return INSTANCE;
}
}
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
System.out.println(instance1);
System.out.println(instance2);
}
我們可以通過我們的反編譯class字節文件,發現有一個帶參構造,如果這個時候再去用反射破壞,發現就會報menu不能被反射的錯了。
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> de = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
de.setAccessible(true);
EnumSingle instance2 = de.newInstance();
}
3.3 靜態內部類實現單例
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
4.建造者模式
將一個復雜對象的構建與他的表示層分離,實現了解耦,使得同樣的構造過程可以構造不同的表示。具體的建造者類之間是相互獨立的,這有利於系統的擴展,符合“開閉原則”。
缺點:共性、內部變化復雜,導致多個建造者類,系統龐大。
(1)舉例工人蓋房子,有四個步驟地基、鋼筋、水泥、粉刷。工人有指揮官,負責指揮工作的順序,如果需要建不同的樓,只需要換不同的工人,因為工人的技能是不同的。
//抽象的建造者:方法
public abstract class Builder {
abstract void buildA(); //地基
abstract void buildB(); //鋼筋
abstract void buildC(); //鋪電線
abstract void buildD(); //粉刷
//完工:得到產品
abstract Product getProduct();
}
//產品:房子
public class Product {
private String builderA;
private String builderB;
private String builderC;
private String builderD;
public String getBuilderA() {
return builderA;
}
public void setBuilderA(String builderA) {
this.builderA = builderA;
}
public String getBuilderB() {
return builderB;
}
public void setBuilderB(String builderB) {
this.builderB = builderB;
}
public String getBuilderC() {
return builderC;
}
public void setBuilderC(String builderC) {
this.builderC = builderC;
}
public String getBuilderD() {
return builderD;
}
public void setBuilderD(String builderD) {
this.builderD = builderD;
}
@Override
public String toString() {
return "Product [builderA=" + builderA + ", builderB=" + builderB + ", builderC=" + builderC + ", builderD="
+ builderD + "]";
}
}
public class Worker extends Builder{
private Product product;
public Worker() {
product = new Product();
}
//工人把工作,實現建造的沒有步驟
public void buildA() {
product.setBuilderA("地基");
System.out.println("地基");
}
public void buildB() {
product.setBuilderB("鋼筋");
System.out.println("鋼筋");
}
public void buildC() {
product.setBuilderC("鋪電線");
System.out.println("鋪電線");
}
public void buildD() {
product.setBuilderD("粉刷");
System.out.println("粉刷");
}
public Product getProduct() {
return product;
}
}
//指揮:核心,負責指揮構建一個工程,工程如何構建由他決定
public class Director {
//指揮工人按照順序建房子
public Product build(Builder builder) {
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
public class Test {
public static void main(String[] args) {
//需要一個指揮者
Director director = new Director();
//指揮工人去完成具體的產品
Product product = director.build(new Worker());
System.out.println(product.toString());
}
}
(2)上述是有指揮官的構造,如果沒有指揮官,就不需要順序,也就是實現無序的搭配。
舉例:麥當勞的套餐,服務員(建造者)隨意搭配幾種食物形成套餐銷售給客戶。
//抽象的建造者:方法
public abstract class Builder {
abstract Builder buildA(String msg); //漢堡
abstract Builder buildB(String msg); //可樂
abstract Builder buildC(String msg); //薯條
abstract Builder buildD(String msg); //甜品
//完工:得到產品套餐
abstract Product getProduct();
}
//產品:套餐
public class Product {
//默認值,也就是有默認的套餐
private String builderA = "漢堡";
private String builderB = "可樂";
private String builderC = "薯條";
private String builderD = "甜點";
public String getBuilderA() {
return builderA;
}
public void setBuilderA(String builderA) {
this.builderA = builderA;
}
public String getBuilderB() {
return builderB;
}
public void setBuilderB(String builderB) {
this.builderB = builderB;
}
public String getBuilderC() {
return builderC;
}
public void setBuilderC(String builderC) {
this.builderC = builderC;
}
public String getBuilderD() {
return builderD;
}
public void setBuilderD(String builderD) {
this.builderD = builderD;
}
@Override
public String toString() {
return "Product [builderA=" + builderA + ", builderB=" + builderB + ", builderC=" + builderC + ", builderD="
+ builderD + "]";
}
}
public class Worker extends Builder{
private Product product;
public Worker() {
product = new Product();
}
public Builder buildA(String msg) {
product.setBuilderA(msg);
return this;
}
public Builder buildB(String msg) {
product.setBuilderB(msg);
return this;
}
public Builder buildC(String msg) {
product.setBuilderC(msg);
return this;
}
public Builder buildD(String msg) {
product.setBuilderD(msg);
return this;
}
public Product getProduct() {
return product;
}
}
public class Test {
public static void main(String[] args) {
//服務員
Worker worker = new Worker();
//不改變參數,默認套餐
//Product product = worker.getProduct();
//鏈式編程:在原來的基礎上可以自由組合了,如果不組合也有默認的套餐。
Product product = worker.buildA("披薩").buildB("美年達").getProduct();
System.out.println(product.toString());
}
}
5.原型模式
原型模式是以一個為原型,在使用的時候不用再去new ,而是通過clone來提升效率。
第一步,實現一個接口 Cloneable;第二步,重寫一個方法 clone();
(1)淺克隆:通過克隆出來的對象和原對象是一樣的,但是屬性的引用也是一樣的,指向同一塊內存空間。
public class Video implements Cloneable{
private String name;
private Date createTime;
//淺克隆
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {}
public Video(String name,Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String toString() {
return "Video [name=" + name + ", createTime=" + createTime + "]";
}
}
測試如下:
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
//原型對象
Date date = new Date();
Video v1 = new Video("狂神說",date);
System.out.println("v1:"+v1);
System.out.println("v1.hashcode():"+v1.hashCode());
//clone一個對象
Video v2 = (Video)v1.clone(); //克隆出來的對象和原來的對象是一摸一樣的
System.out.println("v2:"+v2);
System.out.println("v2.hashcode():"+v2.hashCode());
v2.setName("clone:狂神說");
System.out.println(v2);
System.out.println("=====但是這樣是淺克隆,date會隨着原型值的改變而改變=====");
date.setTime(123135);
System.out.println("v1:"+v1);
System.out.println("v2:"+v2);
}
}
(2)深克隆:克隆對象額同時也會把屬性一同克隆,使克隆的屬性擁有自己獨立的內存空間。還有序列化,反序列化。
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
Video v = (Video)obj;
//克隆屬性
v.createTime = (Date)this.createTime.clone();
return obj;
}