1. 程式人生 > 實用技巧 >23種設計模式之七種結構型模式從概念介紹再到案例分析,不懂就從例子去感受

23種設計模式之七種結構型模式從概念介紹再到案例分析,不懂就從例子去感受

二、結構型模式(Structure Pattern)

1.介面卡模式

將一個類的介面轉換成客戶希望的另外一個介面,使得不能相容的而能在一起工作。

(1)類的介面卡:當希望將一個類轉換成另一個新介面的類時,可以使用類的介面卡模式,建立一個新類,繼承原來的類,實現新的介面。

舉例:電腦網線介面(目標介面)、網線(需要適配的類)和網線介面卡(介面卡)。此時應該時將網線轉換成電腦網線介面,我們用類的介面卡實現類去繼承網線類並實現抽象出來的介面卡介面——相當於把網線插在了介面卡上。

//要被適配的類: 網線
public class NetLine {
	public void request() {
		System.out.println("連線網線上網");
	}
}
//真正的是介面卡,需要連線USB和網線
public class Adapter extends NetLine implements NetToUSB{
	@Override
	public void handleRequest() {
		super.request();	//可以上網了
	}
}
//客戶端類:想上網,插不上網線
public class Compter {

	public void net(NetToUSB adapter) {
		//上網的具體實現:  找一個轉接頭
		adapter.handleRequest();
	}
	
	public static void main(String[] args) {
		//電腦、介面卡、網線
		Compter compter = new Compter();
		Adapter adapter = new Adapter();
		compter.net(adapter);	//這在因為介面卡是繼承了網線所以相當與介面卡直接把網線插上了,只要在電腦上插上介面卡就行
	}
}

(2)物件介面卡:當希望將一個物件轉換成滿足另一個新介面的物件時,可以建立網線介面卡的一個類,持有網線的一個例項,在該類中呼叫例項的方法。

public class Adapter2 extends NetLine implements NetToUSB{

	private NetLine netLine;
	public Adapter2(NetLine netLine) {
		this.netLine = netLine;
	}
	@Override
	public void handleRequest() {
		netLine.request();	//可以上網了
	}
}
public class Compter {

	public void net(NetToUSB adapter) {
		//上網的具體實現:  找一個轉接頭
		adapter.handleRequest();
	}
	
	public static void main(String[] args) {
		//電腦、介面卡、網線
		Compter compter2 = new Compter();
		NetLine netLine2 = new NetLine();
		Adapter2 adapter2 = new Adapter2(netLine2);
		compter2.net(adapter2);	
	}
}

使用場景:系統使用的現有類和老系統以前的類的相容可能會需要適配介面。

(3)介面的介面卡模式:當我們不希望去實現介面中的所有方法的時候,可以去建立一個抽象類,實現所有的方法,我們在寫別的類的時候,繼承抽象類即可。

介面:

public interface Sourceable{
	void method1();
	void method2();
}

抽象類:

public abstract class Wrapper implements Sourceable{
	public void method1();
	public void method2();
}
public class SourceSub1 extends Wrapper{
	public void method1(){
		System.out.println("first sub1");
	}
}
public class SourceSub2 extends Wrapper{
	public void method2(){
		System.out.println("second sub2");
	}
}
public class WrapperTest{
	public satic void main(String[] args){
		Sourceable s1 = new Sourcesub1();
		Sourceable s2 = new Sourcesub2();
		s1.method1();
		s1.method2();
		s2.method1();
		s2.method2();	
}

結果是:first Sub1 、second Sub2;

2.橋接模式

橋接模式偶爾類似於多繼承方案,但是多繼承違背了單一職責原則,橋接模式可以避免,提高了系統的擴充套件性。在兩個維度任意一個維度的擴充套件都不需要修改原有系統。

舉例:電腦和品牌的隨意組合。

//電腦的品牌
public interface Brand {
	void info();
}
//蘋果品牌
public class Apple implements Brand{
	@Override
	public void info() {
		System.out.print("蘋果");
	}
}
//聯想品牌
public class lenovo implements Brand{
	@Override
	public void info() {
		System.out.print("聯想");
	}
}
//抽象的電腦型別
public abstract class Compter {

	//使用組合,品牌
	protected Brand brand;

	public Compter(Brand brand) {
		this.brand = brand;
	}
	
	public void info() {
		brand.info();  //電腦出廠自帶品牌設定
	}
	
}

public class Laptop extends Compter{

	public Laptop(Brand brand) {
		super(brand);
	}
	public void info() {
		super.info();
		System.out.println("筆記本");
	}
}
public class Desktop extends Compter{

	public Desktop(Brand brand) {
		super(brand);
	}
	public void info() {
		super.info();
		System.out.println("桌上型電腦");
	}
}
public class Test {

	public static void main(String[] args) {
		//蘋果筆記本
		Compter compter = new Laptop(new Apple());
		compter.info();
		//聯想桌上型電腦
		Compter compter2 = new Desktop(new lenovo());
		compter2.info();
	}
}

最佳實踐:如果一個系統需要在構建抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承關係。

應用場景:Java語言通過Java虛擬機器實現了平臺的無關性,一次編譯,到處執行;JDBC驅動程式也是橋接模式的應用之一,可以連線各種資料庫。

3.代理模式

為什麼要學習代理模式,代理模式是SpringAOP的底層。

  • 靜態代理

  • 動態代理

3.1 靜態代理

角色分析:

  • 抽象角色:一般會使用介面和抽象類來解決。
  • 真實角色:被代理的角色。
  • 代理角色:代理真實角色,代理真實角色後,一般有附屬操作。
  • 客戶:訪問代理物件的人。
//以租房子為例
public interface Rent {

	public void rent();
}
//房東
public class Host implements Rent{
	@Override
	public void rent() {
		System.out.println("房東要出租房子!");
	}
}
//中介
public class Proxy implements Rent{

	private Host host;

	public Proxy() {
	}
	
	public Proxy(Host host) {
		this.host = host;
	}

	//中介出租房子,實際就是房東去出租
	public void rent() {
		host.rent();
		fee();
	}
	//中介的附屬操作
	public void fee() {
		System.out.println("中介去收中介費!");
	}
}
//客戶
public class Client {

	public static void main(String[] args) {
		Host host = new Host();
		//代理
		Proxy proxy = new Proxy(host);
		proxy.rent();
	}
}

代理模組的好處:

  • 可以使真實的角色更加的純粹!不用去關注一些公共的業務。
  • 公共就交給代理角色,實現了業務的分工。
  • 公共業務發生擴充套件時,方便集中管理。

缺點:一個真實角色就會產生一個代理角色,程式碼量會翻倍,開發效率會變低。

增強程式碼:對應代理模式的橫切操作。

3.2 動態代理
  • 動態代理和靜態代理角色一樣。
  • 動態代理的代理類是動態生成的,不是我們直接寫好的。
  • 動態代理分為兩大類:基於介面的動態代理類、基於類的動態代理類。
    • 基於介面:JDK 動態代理【我們在這裡使用的】
    • 基於類:cglib
    • java位元組碼實現:javasisit

需要了解兩個類:

  • Proxy :代理
  • InvocationHandler :呼叫處理程式

上面的Rent、Host不變的情況下

//等會我們用這個類,自動生成代理類
public class ProxyInvocationHandle implements InvocationHandler{

	//被代理的介面
	private Rent rent;
	
	public void setRent(Rent rent) {
		this.rent = rent;
	}
	
	//生成得到代理類
	public Object getProxy() {
		return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
	}
	
	//處理代理例項,並返回
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		fare();
		//動態代理的本質,就是使用反射機制去實現
		Object result = method.invoke(rent, args);
		return result;
	}
	public void fare() {
		System.out.println("交中介費用");
	}	
}
public class Client {

	public static void main(String[] args) {
		//真實角色
		Host host = new Host();
		//代理角色:現在沒有
		ProxyInvocationHandle pth = new ProxyInvocationHandle();
		//通過呼叫程式處理角色來處理我們要呼叫的介面物件
		pth.setRent(host);
		Rent proxy = (Rent)pth.getProxy();
		proxy.rent();
	}
}

如果把生成代理做成一個模板的話,並且在租房上增加方法。

//以租房子為例
public interface Rent {

	public void rent();
	public void rentTime();
}
//房東
public class Host implements Rent{
	@Override
	public void rent() {
		System.out.println("房東要出租房子!");
	}
	@Override
	public void rentTime() {
		System.out.println("房東要求必須以一年起租!");
	}
}
//等會我們用這個類,自動生成代理類
public class ProxyInvocationHandle implements InvocationHandler{

	//被代理的介面
	private Object target;
	public void setRent(Object target) {
		this.target = target;
	}
	
	//生成得到代理類
	public Object getProxy() {
		return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}
	
	//處理代理例項,並返回
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		log(method.getName());
		//動態代理的本質,就是使用反射機制去實現
		Object result = method.invoke(target, args);
		return result;
	}
	public void log(String msg) {
		System.out.println("執行了"+msg+"方法");
	}
}
public class Client {
	public static void main(String[] args) {
		//真實角色
		Host host = new Host();
		//代理角色,不存在
		ProxyInvocationHandle pth1 = new ProxyInvocationHandle();
		//設定要代理的物件
		pth1.setRent(host);
		//動態生成代理類
		Rent proxy1 = (Rent)pth1.getProxy();
		//呼叫例項
		proxy1.rentTime();
	}
}

動態代理的優點:

  • 一個動態代理類代理的是一個介面,一般就是對應的一類業務。
  • 一個動態代理類可以代理多個類,只要是實現了同一個介面即可。
4.裝飾者模式

在不改變原有物件基礎上,將功能附加到物件上,使物件的關聯關係代替繼承關係,在物件的功能擴充套件方面,它比繼承更具有彈性,符合“閉包原則”。

舉例:給汽車新增自動,飛翔的功能。

//汽車介面
public interface Car {
	void move();
}
//汽車的一個真實角色
public class CarImpl implements Car{
	@Override
	public void move() {
		System.out.println("汽車在陸地上跑");
	}
}
//裝飾者的類
public class Decorator implements Car{

	private Car car;
	public Decorator(Car car) {
		this.car = car;
	}
	@Override
	public void move() {
		car.move();
	}
}
//裝飾一:給汽車新增飛翔的功能
public class FlyCar extends Decorator{

	public FlyCar(Car car) {
		super(car);
	}
	public void fly() {
		System.out.println("汽車在天上飛");
	}
	public void move() {
		super.move();
		fly();
	}
}
//裝飾二:給汽車新增自動的功能
public class AICar extends Decorator{
	
	public AICar(Car car) {
		super(car);
	}
	public void autoMove() {
		System.out.println("汽車自主跑");
	}
	public void move() {
		super.move();
		autoMove();
	}
}
public class Client {

	public static void main(String[] args) {
		Car car = new CarImpl();
		car.move();
		System.out.println("------增加一個功能------");
		Car flyCar = new FlyCar(car);
		flyCar.move();
		System.out.println("------增加兩個功能------");
		Car AotuflyCar = new AICar(flyCar);
		AotuflyCar.move();
	}
}

結果展示:

5.外觀模式Facade(門面模式)

他隱藏了系統的複雜性,並向客戶端提供了一個可以訪問系統的介面。

假設我們要去給一個人傳送三個資料庫安裝包。

class Mysql{
	public void method1() {
		System.out.println("安裝Mysql資料庫桌面工具");
	}
}
class Oracle{
	public void method2() {
		System.out.println("安裝Oracle資料庫桌面工具");
	}
}
class DB2{
	public void method3() {
		System.out.println("安裝DB2資料庫桌面工具");
	}
}

有一個人需要我們就需要傳送三次,太麻煩,我們可以把這三個資料庫壓縮在一個包裡,就只用傳送一IC了。同時,每個接受的人看起來也沒有那麼的凌亂。

class Facade{
	Mysql mysql = new Mysql();
	Oracle oracle = new Oracle();
	DB2 db2 = new DB2();
	//提供一個對外方法
	public void dosomething() {
		mysql.method1();
		oracle.method2();
		db2.method3();
	}
}
class Client{
	Facade f = new Facade();
	public void method() {
		f.dosomething();
	}
	
}

測試:

public class Test {

	public static void main(String[] args) {
		Client client = new Client();
		client.method();
	}	
}
6.享元模式

以共享的方式高效的支援大量的細粒度物件。通過複用記憶體中已存在的物件,降低系統建立物件例項的效能消耗。java的 String 型別就是享元模式。

舉例:很多地圖遊戲中會有很多樹,實際上這樹只有幾個種類,然後同一種類的引用一個物件。

//建立一個樹為例子
class Tree{
	
	//final一旦建立就不可再被修改,保證在多執行緒下的一個安全
	private final String name;
	private final String data;
	
	public Tree(String name,String data) {
		System.out.println(name+"被建立"+data);
		
		this.name = name;
		this.data = data;
	}
	public String getName() {
		return name;
	}
	public String getData() {
		return data;
	}
}
//樹的一個地點位置
class TreeNode{
	private int x;
	private int y;
	private Tree tree;
	
	public TreeNode(int x,int y,Tree tree) {
		this.x=x;
		this.y=y;
		this.tree=tree;
	}
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	public Tree getTree() {
		return tree;
	}
	public void setTree(Tree tree) {
		this.tree = tree;
	}
}
//來一個樹工廠
class TreeFactory{
	//有各種型別的樹,通過樹的名稱不同來建立樹
	private static Map<String,Tree> map = new ConcurrentHashMap<>();
	
	public static Tree getTree(String name,String data) {
		if(map.containsKey(name)) {		//判斷是否有這種樹
			return map.get(name);
		}
		Tree tree = new Tree(name, data);
		map.put(name,tree);
		return tree;
	}
}
public class Test {

	public static void main(String[] args) {
		TreeNode tn1 = new TreeNode(3, 4, TreeFactory.getTree("楊樹", "20米"));
		TreeNode tn2 = new TreeNode(4, 4, TreeFactory.getTree("楊樹", "20米"));
		
		TreeNode tn3 = new TreeNode(3, 4, TreeFactory.getTree("柳樹", "10米"));
		TreeNode tn4 = new TreeNode(4, 4, TreeFactory.getTree("柳樹", "10米"));
	}
}

7.組合模式

將物件聚合成樹形結構來表現“整體/部分”的層次結構,組合模式能讓客戶以一致的方式來處理個別對象以及物件組合,也就是我們可以忽略物件組合與個體物件之間的差別。

舉例:一個學校院系結構,要在一個展示一個學校有多少個學院,一個學院有多少系(專業)。

原理結構圖分析:

  1. Component:這是組合中物件宣告介面,在適當情況下,實現所有類共有的介面預設行為,用於訪問和管理Component子部件,Component可以是抽象類或介面。
  2. Leaf: 在組合中表示葉子節點,葉子節點沒有子節點。
  3. Composite:非葉子節點,用於儲存子部件,在Component介面中實現子部件的相關操縱,不如增刪改查。
//學校、學院、系的父類
public abstract class OrganizationComponent {

	private String name; //名字
	private String des; //描述
	
	protected void add(OrganizationComponent organizationComponent) {
		//預設實現
		throw new UnsupportedOperationException();
	}
	protected void remove(OrganizationComponent organizationComponent) {
		//預設實現
		throw new UnsupportedOperationException();
	}
	//構造方法
	public OrganizationComponent(String name, String des) {
		super();
		this.name = name;
		this.des = des;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getDes() {
		return des;
	}
	public void setDes(String des) {
		this.des = des;
	}
	//方法print,做成抽象的,子類都需要
	protected abstract void print();
}
//University就是Composite,可以管理College
public class University extends OrganizationComponent{

	List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
	public University(String name, String des) {
		super(name, des);
	}

	@Override
	protected void print() {
		System.out.println("----------"+getName()+"----------");
		//遍歷organizationComponents 輸出學校包含的學院
		for(OrganizationComponent organizationComponent:organizationComponents) {
			organizationComponent.print();
		}
	}
	//重寫add()
	protected void add(OrganizationComponent organizationComponent) {
		organizationComponents.add(organizationComponent);
	}
	//重寫remove()
	protected void remove(OrganizationComponent organizationComponent) {
		organizationComponents.add(organizationComponent);
	}
	public String getName() {
		return super.getName();
	}
	public String getDes() {
		return super.getDes();
	}
}
public class College extends OrganizationComponent{

	//這裡放的是一個學院裡有多少個系
	List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
	public College(String name, String des) {
		super(name, des);
	}

	@Override
	protected void print() {
		System.out.println("----"+getName()+"----");
		//遍歷organizationComponents 輸出學校包含的學院
		for(OrganizationComponent organizationComponent:organizationComponents) {
			organizationComponent.print();
		}
	}
	//重寫add()
	protected void add(OrganizationComponent organizationComponent) {
		//將來實際業務,College的add和University add不一定完全一樣 
		organizationComponents.add(organizationComponent);
	}
	//重寫remove()
	protected void remove(OrganizationComponent organizationComponent) {
		organizationComponents.add(organizationComponent);
	}
	public String getName() {
		return super.getName();
	}
	public String getDes() {
		return super.getDes();
	}
}
//系
public class Department extends OrganizationComponent{

	public Department(String name, String des) {
		super(name, des);
	}
	@Override
	protected void print() {
		System.out.println(getName());
	}
	//add ,remove就不要用重寫了,因為是葉子節點
	public String getName() {
		return super.getName();
	}
	public String getDes() {
		return super.getDes();
	}
}
public class Client {
	public static void main(String[] args) {
		//從小到大進行建立
		OrganizationComponent university = new University("清華大學", "中國頂級大學");
		
		//建立學院
		OrganizationComponent computerCollege = new College("計算機學院", "計算機");
		OrganizationComponent infoEngineerCollege = new College("資訊工程學院", "資訊工程");
		
		//建立專業並加入到學院中
		computerCollege.add(new Department("軟體工程", "軟體"));
		computerCollege.add(new Department("網路工程", "網路"));
		computerCollege.add(new Department("電腦科學與技術", "電腦科學與技術"));
		infoEngineerCollege.add(new Department("通訊工程", "通訊"));
		infoEngineerCollege.add(new Department("資訊工程", "資訊"));
		
		//將學院新增到學校
		university.add(computerCollege);
		university.add(infoEngineerCollege);
		
		//輸出整個學校結構
		university.print();
		//輸出計算機學院結構
		System.out.println("=================================");
		computerCollege.print();
	}
}