Java:一篇學好設計模式

2023-04-13 12:00:26

什麼是設計模式

簡單理解,設計模式是前人多年寫程式碼踩坑總結出來的優秀程式碼攻略,目的是減少大量無用程式碼,讓專案更好維護

七大設計原則

接下來要講的23種設計模式,但遵循下面的七大原則:

  1. 單一職責原則
    2、介面隔離原則
  2. 依賴倒置原則
    4、里氏替換原則
  3. 開閉原則
  4. 迪米特原則
  5. 合成複用原則

單一職責原則

單一職責原則,目的是每個類履行一種職責。好比一個人是醫生,就好好幹醫生份內事,無需插手護士、保潔等和醫生職責無關事

反例

比如說一個Person類,有工作work的方法:

public class Person{
	public void work(String person){
		System.out.println("職位為:"+ person + "的人正在工作");
	}
}

這個Person,根據傳的person能做各種職位的事,破壞了單一職責

正例

public interface Person{
	public void work();
}
public class Doctor implement Person{
	public void work(){
	}
}

public class Teacher implement Person{
	public void work(){
	}
}

介面隔離原則

這個原則有點難講,總結就是把介面拆成最簡。

反例

有一個Person,有 fight打人、eat吃飯、work工作三個方法

public interface Person{
	public void fight();
	public void eat();
	public void work();
}

現在有兩個類:PersonA 和 PersonB 都實現了Person介面,實際上PersonA根本不需要打人,PersonB不需要工作。

正例

public interface Person1{
	public void eat();
}

public interface Person2 extend Person1{
	public void fight();
}

public interface Person3 extend Person1{
	public void work();
}

拆成3個介面。如此一來,PersonA只需要實現Person3,PersonAB實現Person2

依賴倒置原則

依賴倒置原則,核心在於面向介面程式設計,傳細節,定抽象。

反例

有個Person類,有send方法。

public class Person{
	public void send(Email mail){
		System.out.println(mail.getInfo());
	}
}

這方法只能發郵件,那我傳簡訊啥的就不行了

正例

定義3個介面

public interface Receiver{
	public void getInfo();
}

public class Email implement Receiver{
	public void getInfo(){
	
	}
}

public class WeiXin  implement Receiver{
	public void getInfo(){
	
	}
}

結果:

public class Person{
	public void send(Receiver r){
		System.out.println(mail.getInfo());
	}
}

里氏替換原則

里氏替換原則,他的核心在於:儘量子類不用去重寫父類別的方法。

開閉原則

開閉原則,核心在於:對外允許拓展,對內拒絕修改。
什麼意思呢?比如現在我給你了空的羽毛球圓筒,對外只需要裝合適的羽毛球即可,對內是不拒絕像把羽毛球筒硬搞成方的

再舉個例子,一些組態檔有些引數是固定好的,比如 server.port。對內可以編寫自己的引數,對內是不允許修改server.port的引數名

迪米特原則

迪米特原則,被依賴的類儘量讓依賴類知道的內部實現細節儘量少,總結就是降低耦合

合成複用原則

合成複用原則,當使用另一個類的方法時,儘量使用聚合的方式,減少使用繼承。

這個比較好理解,現在有A類有個A1方法,B想要使用A1。不好的做法就是B去繼承A,然後就有A1方法。推薦是B裡引入A,然後呼叫A去用A1

設計模式

設計模式可分為3種型別,下面列舉需要重點掌握的設計模式

建立型模式:單例模式,工廠模式,原型模式,建造者模式
結構型模式:介面卡模式,裝飾模式,代理模式
行為型模式:觀察者模式

單例模式

單例模式,要求在整個應用中一個類只有一個物件範例。
剛開始此模式,懶漢和餓漢式的單例程式碼比較經典,大家有空去看下。

然後在spring最常見的單例就是我們的元件,預設都是單例的,也是單例的體現。

工廠模式

簡單工廠模式

比如車有很多品牌:奧迪、寶馬、保時捷...如果我要一輛寶馬,那我得手動new,要一輛保時捷還是得new。那簡單工廠模式,在這裡可以定義一個建立車工廠類,傳入引數拿到不同品牌車。如:

Car car = CarFactory.getCar(String brand);

抽象工廠模式

抽象工廠模式是簡單工廠模式的升級版。簡單工廠模式從一個工廠拿不同牌子的車,那如果某個牌子中細分有車型,顏色....那單個工廠顯然不夠用了

利用抽象工廠模式,可以從工廠方法A中拿到某個牌子的工廠B,再根據B拿到具體的車,如下:

BrandCarFactory brandCarFactory = CarFactory.getBrandCarFactory(String brand);

Car car = brandCarFactory.getCar(String color,String size....)

原型模式

原型模式,通過原型物件構建出和他屬性一樣的範例。比如說建立10只羊,每次羊都叫喜羊羊,18歲。當第一隻建立後,第二隻的建立屬性都從第一支獲取

spring的bean設定為多例時,利用到的就是原型模式。

建造者模式

建造者模式,和工廠模式有點類似。說起來有點難講,還是看下例子:

  1. 執行入口:
public class Demo{
	public static void main(String[] args) {
        HouseDiretor dir = new HouseDiretor(new HightHouse());
		House house = dir.buildHouse();
    }
}
  1. 各個類:
//房子構造者
public abstract class HouseBuilder{
	private House house = new House();
	
	public void buildA();
	public void buildB();
	public void buildC();
	
	public void buildHouse(){
		return house;
	}
}

//高樓
public class HightHouse extends HouseBuilder{
	public void buildA(){
	...
	}
	public void buildB(){
	...
	}
	public void buildC(){
	....
	}
	
}

//指揮者
public class HouseDiretor{
	private HouseBuilder builder;
	
	public HouseDiretor(HouseBuilder builder){
		this.builder = builder;
	}
	
	public void buildHouse(){
		builder.buildA();
		builder.buildB();
		builder.buildC();
		return builder.buildHouse();
	}
}

介面卡模式

介面卡模式,就很好理解了。
平時大家見過的轉接頭就是介面卡,比如你筆電只有USB介面,但插口是HIDM,這就需要一個HIDM轉USB的轉換器充當連線。

類介面卡模式

下面以充電器將220V轉5V電壓作為介面卡來編寫例子:
5V介面:

public interface Inter5V{
	public int output5V();
}

220V實現類

public class Impl220V {
	public int output220V(){
		return 220;
	}
}

介面卡類

public class Impl5V extends Impl220V implement Inter5V{
	public int output5V(){
		return output220V()/24
	}
}

比如現在手機要充電

public class Phone{
	public void charging(Inter5V inter5V){
		if(inter5V.output5V == 5){
		 //可以充電了
		}else{
			//不能充電
		}
	}
}

    public static void main(String[] args) {
        Phone phone = new Phone();
		phone.charging(new Impl55V)
    }

物件介面卡模式

類介面卡模式在於繼承,這種方式有侷限性。
而物件介面卡模式在於聚合,沒有繼承關係限制。還是按上面的例子,但是某些類發生變化:
介面卡類

public class Impl5V implement Inter5V{
	private Impl220V impl220V;;
	
	public Impl20V(Impl220V impl220V){
		this.impl220V = impl220V;
	}
	public int output5V(){
		return impl220V.output220V()/24
	}
}

充電入口:

public static void main(String[] args) {
        Phone phone = new Phone();
		phone.charging(new Impl55V(new Impl220V()))
 }

介面介面卡模式

介面介面卡模式,和上面兩種不太一樣。他解決是這樣一個問題:介面的方法不想全部實現,只實現自己要用的即可

介面介面卡往往會定義抽一個抽象類,去實現A介面的所有方法,但是方法體全為空。然後我們再去繼承抽象類,選擇性重寫自己的方法即可。

裝飾者模式

裝飾者模式,在於解決銀排列組合問題。就比如說牛肉麵,排骨麵...這些都是固定死的,理想狀態是一碗麵,想加啥自己加。
在Java在裝飾罩的體現,有個很重要的前提,就是被裝飾著和裝飾者都會實現或繼承相同的父類別,具體參考如下:
https://blog.csdn.net/m0_47944994/article/details/127901010

裝飾者A a = new 裝飾者A(new 被裝飾者)
裝飾者B b = new 裝飾者B(new 被裝飾者)

代理模式

代理模式,在不改動A方法的基礎上,代理類(增強類)對A方法進行前後的增強。

代理模式可以分為三種:靜態代理,動態代理(JDK代理,介面代理),cglib代理

靜態代理

靜態代理,有個重要的前提是:代理類和被代理類都要實現同一介面,直接上例子:
同一介面:

public interface ITeateacher{
	public void teach();
}

被代理類:

public class Teacher implments ITeateacher{
	public void teach(){
		System.out.println("教學生");
	}
}

代理類:

public class TeacherProxy implments ITeateacher{
	public ITeateacher teacher;
	
	public TeacherProxy(ITeateacher teacher){
		this.teacher = teacher;
	}
	public void teach(){
		System.out.println("備課");
		teacher.teacher();
		System.out.println("下課");
	}
}

使用:

public static void main(String[] args) {
	ITeateacher teacher = new TeacherProxy(nwe Teacher());
	teacher.teach()
}

好處就不說了,就是方法增強了。但缺點是實現同一介面,後面介面方法進行拓展不好維護

動態代理

動態代理也被叫JDK代理,因為代理物件是依賴JDK的API來生成的,不需要我們去建立代理類。

前提要求是目標類是要實現介面的,並且是對介面的所有方法進行增強,範例如下:

目標類介面:

public interface ITeacherDao {
    public String teach(String person);
}

目標類:

public class TeacherDao implements ITeacherDao{
    @Override
    public String teach(String person) {
        return person + "在教書";
    }
}

代理工廠類,我們編寫用於獲取代理物件:

public class ProxyFactory {
	public ProxyFactory(Object targetObj){
        this.targetObj = targetObj;
    }
	
	public Object getProxyIntance(){
        	return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), targetObj.getClass().getInterfaces()
                , new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("增強前程式碼");
                        Object returnValue = method.invoke(targetObj, args);
                        System.out.println(returnValue);
                        System.out.println("增強後程式碼");
                        return returnValue;
                    }
                });
    }
}

使用:

public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());

        //獲取代理物件(JDK幫我們生成的)
        ITeacherDao proxyIntance = (ITeacherDao) proxyFactory.getProxyIntance();

        proxyIntance.teach("張三");
    }

動態代理生成代理物件其實也實現了介面,和靜態一樣。不一樣是代理物件是JDK幫我們做了而已

cglib代理

cglib代理,如果說目標類是不需要實現任何介面的,那麼就用不了動態代理,但是cglib代理可以解決

注意cglib是一個框架包,需要諮詢引入使用。下面直接上程式碼:
目標類:

public class TeacherDao{

    public String teach(String person) {
        return person + "在教書";
    }
}

代理工廠類:

public class ProxyFactory implements MethodInterceptor {
    private Object targetObj;

    public ProxyFactory(Object targetObj){
        this.targetObj = targetObj;
    }

    public Object getProxyIntance(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetObj.getClass());//設定父類別,cglib原理就是通過繼承目標類來生成子類(代理類)
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib增強,增強前----");
        Object returnValue = method.invoke(targetObj, args);
        System.out.println(returnValue);
        System.out.println("cglib增強,增強後----");
        return returnValue;
    }
}

使用

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());

        //獲取代理物件(JDK幫我們生成的)
        TeacherDao proxyIntance = (TeacherDao) proxyFactory.getProxyIntance();

        proxyIntance.teach("張三");
    }

模板模式

模板模式很簡單,就是定義了一套流程所需的介面和並且子類實現。比如RedisTemplate,JdbcTemplate