软件设计模式---结构型模式

结构型模式

结构型模式概述

结构型模式描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木组合形成复杂的、功能更更为强大的结构

image-20230102152451634

结构型模式可以分为类结构型模式对象结构型模式

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
  • 对象结构模型模式关心类与对象的组合,,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构性模式

结构型模式简介

image-20230102152807852

适配器模式

模式动机

笔记本电脑的工作电压是20v,而我国的家庭用电是220v,然和让20v的地笔记本电脑能在220v的电压下工作?

image-20230102152926723

引入一个电源适配器(AC Adapter),俗称充电器或变压器,有了这个电源适配器,生活用电和笔记本电脑即可兼容。

在软件开发中,有时也存在类似这种不兼容的情况,我们也可以像引入一个电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式

-huntBird游戏中,希望增加一种鸟类“鸭子”

-但是发现以前有一个系统已经有了鸭子类,希望重用老代码

image-20230102153139243

新老代码接口不一致,怎么复用老代码?

image-20230102153333736

把老代码修改一下不就可以了吗?如下

image-20230102153349418

否定,

  • 首先老代码不一定允许修改,比如可能根本木有代码,只有链接库
  • 其次,修改代码工作量可能很大
  • 容易出错,还记得开闭原则么

应用(对象)适配器模式实现接口转换

image-20230102153652603

image-20230102153842909

理解:重新包装,改变接口

模式动机

-在适配器模式中可以定一个包装类,包装不兼容接口的对象,这个包装类值的就是适配器,他所包装对象的就是适配者,即被适配的类

-适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转换为对适配者的相应接口的调用

模式定义

适配器模式:将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式

模式结构
类适配器

  • target:目标抽象类
  • adapter:适配器类
  • adaptee:适配者类
  • client:客户类

典型的类适配器代码

public class Adapter extends Adaptee implements Target
{
    public void request()
    {
        specificRequest();
    }
}
对象适配器

image-20230102154833916

public class Adapter extends Target
{
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee)
    {
        this.adaptee=adaptee;
    }
    public void request()
    {
        adaptee.specificRequest();
    }
}

实例:仿生机器人

现需要设计一个可以模拟各种动物行为的机器人,在机器人中定义了一系列方法,如机器人叫喊方法cry(),机器人移动方法move()等,如果希望在不修改已有代码的基础上使得机器人能够像狗一样叫,像狗一样跑,使用适配器模式进行系统设计

image-20230102155104376

public class Dog
{
    public void wang()
    {
        sout("狗汪汪叫")}
    public void run()
    {
        sout("狗快快跑!");
    }
}
public interface Robot
{
    public void cry();
    public void move();
}
public class DogAdapter extends Dog implements Robot
{
    public void cry()
    {
        sout("机器人模仿")super.wang();
    }
    public void move()
    {
        sout("机器人模仿");
        super.run();
    }
}
public class client
{
    psvm
    {
        Robot robot = (Robot)XMLUtil.getBean();
        robot.cry();
        robot.move();
    }
}

案例:加密适配器

某系统需要提供一个加密码模块,将用户信息(如密码等机密信息)加密之后再存储在数据库中,系统已经定义好了数据库操作类,为了提高开发效率,现需要重用已有的加密码算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码,使用适配器模式设计该加密模块。实现在不修改现有类的基础上重用第三方加密方法

image-20230102155817912

image-20230102160624775

public class CipherAdapter extends Dataoperation
{
    private Caesar cipher;;
    public CipherAdapter()
    {
        cipher = new Caesar();
    }
    public String doEncrypt(int key,String ps)
    {
        return cipher.doEncrypt(key.ps);
    }
}
public abstract class DataOperation
{
    private String password;
    public void setPassword(String password)
    {
        this.password=password;
    }
}
public class NewCipherAdapter extends DataOperation
{
    private NewCipher cipher;
    public NewCipherAdapter()
    {
        cipher=new NewCipher();
        
    }
    public String doEncrypt(int key,String ps)
    {
        return cipher.doEncrypt(key,ps);
    }
}
public final class Caesar 
{
	public String doEncrypt(int key,String ps)
	{   
		String es="";
		for(int i=0;i<ps.length();i++)
		{
			char c=ps.charAt(i);
			if(c>='a'&&c<='z')
			{
				c+=key%26;
			if(c>'z') c-=26;
			if(c<'a') c+=26;
			}
			if(c>='A'&&c<='Z')
			{
				c+=key%26;
			if(c>'Z') c-=26;
			if(c<'A') c+=26;
			}
			es+=c;
		}
		return es;
	}
}

public final class NewCipher
{
	public String doEncrypt(int key,String ps)
	{ 
		String es="";
		for(int i=0;i<ps.length();i++)
		{
			String c=String.valueOf(ps.charAt(i)%key);
			es+=c;
		}
		return es;
	}
}

模式优缺点

适配器模式的优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端来说是透明的,而且提高了适配者的复用性
  • 灵活性和扩展性都非常好通过使用配置文件,可以很方便更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则

类适配器还有如下优点

  • 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强

类适配器模式的缺点如下

  • 对于java、c#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和子类都适配到目标接口

对象适配器还有如下优点

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口

对象适配器模式的缺点如下

  • 与类适配器模式相比,要想置换适配者类的方法就不容易,如果一定要置换调适配者类的一个或多个方法,鸡只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,其实现过程较为复杂
模式适用环境

在以下情况下可以使用适配器模式

  • 系统需要使用现有的类,而这些类的接口不符合系统的需压迫
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能将来引进的类一起工作
模式应用

sun公司在1996年公开了java语言的数据库连接工具jdbc,jdbc使得java语言程序能够与数据库连接,并用sql语言来查询和操作数据库jdbc给出一个客户端通用的抽象接口,每一个具体数据库引擎(如sqlserver oracle mysql)等的jdbc驱动软件都是一个节约jdbc接口和数据库引擎接口之间的适配器软件,抽象的jdbc接口和各个数据库引擎API之间都需要相应的适配器软件,这就是为各个不同数据库引擎准备的驱动程序

在springaop框架中,对beforeAdvice,AfterAdvice,ThrowsAdvice三种通知类型借助适配器模式来实现

public intterface AdvisorAdapter
{
    //将一个Advisor是配成MethodInterceptor
    MethodInterceptor getInterceptor(Advisor advisor);
    //判断此适配器是否支持特定的Advice
    boolean supportsAdvice(Advice advice);
}

(3)在JDK类库中也定义了一系列适配器类,如在com.sun.imageio.plugins.common包中定义的InputStreamAdapter类,用于包装ImageInputStream接口及其子类对象。

public class InputStreamAdapter extends InputStream {
    ImageInputStream stream;
    public InputStreamAdapter(ImageInputStream stream) {
        super();
        this.stream = stream;
    }
    public int read() throws IOException {
        return stream.read();
    }
    public int read(byte b[], int off, int len) throws IOException {
        return stream.read(b, off, len);
    }
} 

组合模式

image-20230102163525862

模式动机

一致的方式处理树形结构,具有递归特征

image-20230102163558296

生活中还有哪些是树形结构?

image-20230102163613111

image-20230102163617961

image-20230102163621644

对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行(递归调用)

由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码必须有区别的对待容器对象和叶子对象,而实际上大多数情况下客户端希望一直的处理他们,因为对于这些对相区别对待将会使得程序非常复杂

模式动机

组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对他们进行区分,可以一致的对待容器对象和叶子对象,这就是组合模式的模式动机

模式定义

组合模式:组合多个对象形成属性结构以表示整体-部分的结构层次,组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性

组合模式又可以称为“整体-部分模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体和部分的关系

image-20230102164651808

组合模式包含如下角色

  • Component:抽象构件
  • Leaf叶子构件
  • Composite容器构架
  • Client:客户类

模式分析

  • 组合模式的关键是定义了一个抽象构件类,它既可以代表叶子又可以代表容器,而客户端针对该抽象构建类进行编程,无需知道他到底代表的是叶子还是容器,可以对其进行统一处理
  • 同时容器对象与抽象构件类之间还建立一个聚合关联关系, 在容器对象中既可以包含叶子,也可以包含容器,一次实现递归组合,形成一个树形结构

文件系统组合模式结构图

image-20230102165341277

image-20230102165344443

典型的抽象构件角色代码
public abstract class Component
{
    public abstract void add(Component c);
    public abstract void remove(Component c);
    public abstract Component getChild(int i);
    public abstract void operation();
}
典型的叶子构件角色代码
public class Leaf extends Component
{
    public void add(Component c)
    {
        异常处理或错误提示
    }
    public void remove(Component c){//异常处理或错误提示}
    public Component getChild(int i){//异常处理或错误提示}
    public void operation()
    {
        //实现代码
    }
}
        
public class Composite extends Component
{
    private ArrayList list = new ArrayList();
    public void add(Component c)list.add(c);
    public void remove(Component c)list.remove(c);
    public Component getChild(int i)(Component)list.get(i);
    public void operation()
    {
        for(Object obj:list)((Component)obj).operation();
    }
}

实例:水果盘

在水果盘(plate)中有一些水果,如苹果香蕉例子,当然大水果盘中还可以有小水果盘。现在需要对盘中的水果进行遍历,当然如果对一个水果盘执行吃方法,实际上就是吃其中的水果,使用组合模式模拟该场景

image-20230102170019204

image-20230102170232621

public abstract class Myelement
{	
	public abstract void eat();
}
public class Pear extends  Myelement
{
    public void eat()
    {
        sout("吃梨子")
    }
}
public class Apple extends Myelement
{
    public void eat()
    {
        sout("吃苹果")
    }
}
public class Banana extends MyElement
{
	public void eat()
	{
		System.out.println("吃香蕉!");
	}
}

public class plate extends Myelement
{
    private ArryayList = new ArryList();
    public void add(Myelement element)
    {
        list.add(element);
    }
    public void delete(MyElement element)
    {
        list.remove(element);
    }
    public void eat()
    {
        for(Object object:lsit)
        {
            ((Myelement)object).eat();
        }
    }
}
public class Client
{
    psvm
    {
        MyElement obj1,obj2,obj3,obj4,obj5;
        Plate plate1,plate2,plate3;
		obj1=new Apple();
        obj2=new Pear();
        plate1=new Plate();
        plate1.add(obj1);
		plate1.add(obj2);
        obj3=new Banana();
		obj4=new Banana();
		plate2=new Plate();
		plate2.add(obj3);
		plate2.add(obj4);
        obj5=new Apple();
		plate3=new Plate();
		plate3.add(plate1);
		plate3.add(plate2);
		plate3.add(obj5);			
		plate3.eat();

    }
}

练习:文件浏览

文件有不同类型不同类型的文件其浏览方式有所区别,如文本文件和图片文件的浏览方式就不相同,对文件夹的浏览实际上就是对其中所包含文件的浏览,而客户端可以一致对文件和文件夹进行操作,无需关心他们的区别,使用组合模式来模拟文件的浏览操作

image-20230102171217500

image-20230102172145636

模式优缺点

可以清楚的定义分层次的复杂对象表示对象的全部或部分层次,使得增加新的构件也很容易

客户端调用简单,客户端可以一致的使用组合结构或其中单个对象

定义了包含叶子对象he容器对象的类层次结构,叶子对象可以备注合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断地跪下去,=可以形成复杂的树形结构

更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码

组合模式缺点

  • 使得设计变成更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战下,而且不是所有的方法都与叶子对象子类都有关联。
  • 增加新构建时可能会产生一些问题,很难对容器中发的构件类型进行限制

模式使用环境

  • 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体和部分的差异,可以一致的对待他们
  • 让客户能够忽略不同对象层次的变化,客户端可以针对想抽象构件编程,无须关心对象层次结构的细节
  • 对象的结构是动态的并且复杂程度不一样,但客户需要一致的处理他们

image-20230102175326447

<?xml version="1.0"?>
  <books>
    <book>
        <author>Carson</author>
        <price format="dollar">31.95</price>
        <pubdate>05/01/2001</pubdate>
    </book>
    <pubinfo>
        <publisher>MSPress</publisher>
        <state>WA</state>
    </pubinfo>
  </books> 

模式应用

操作系统中的目录结构是一个树形结构,因此对文件和温恩减价进行操作时可以应用组合模式,例如杀毒软件在查毒或杀毒时,既可以针对一个具体文件,也可以针对一个目录,如果是对目录查毒或杀毒,将递归处理目录中的每一个子目录和文件

JDK的Awt/swing是组合模式在java类库中的一个典型实际应用

image-20230102175707675

模式扩展

更复杂的组合模式

image-20230102175855167

模式扩展

透明组合模式

image-20230102180209947

安全组合模式

image-20230102180231348

装饰模式

​ 真君到了,问:“兄弟们,赶到那厢不见了?”众神道:“才在这里围住,就不见了。”二郎圆睁凤眼观看,见大圣变了麻雀儿**,**钉在树上,就收了法象,撇了神锋,卸下弹弓,摇身一变,变作个雀鹰儿,抖开翅,飞将去扑打。大圣见了,搜的一翅飞起,去变作一只大鹚老,冲天而去。二郎见了,急抖翎毛,摇身一变,变作一只大海鹤,钻上云霄来衔。大圣又将身按下,入涧中,变作一个鱼儿,淬入水内。

image-20230102180343146

系统中的对象

image-20230102180422098

解决方案

image-20230102180435465

大圣的新本领

变中变

  • 麻雀+鱼

  • 鹚鹄(CiHu)+鱼

  • 麻雀+鹚鹄

  • 麻雀+鹚鹄+鱼

image-20230102180508854

image-20230102180537100

装饰模式解决

image-20230102180804404

齐天大圣

abstract public class dasheng
{
    abstract public void move();
    abstract public void fly();
}
大圣本尊
public class DashengConcreate:Dasheng
{
    public ovveride void move()
    {
        Console.write("monkey-run");
    }
    public override void fly()
    {
        Console.WriteLine("筋斗云");
    }
}
大圣化身
public class DahengDecorator:Dasheng
{
    private Dahseng _self;
    public DashengDecorator(Dasheng dasheng)
    {
        _slef=dasheng;
    }
    public override void move()
    {
        _selef.move();
    }
    public override void fly()
    {
        _self.fly();
    }
}
public class Fish:DashengDecorator
{
    public Fish(Dahsnge dasheng) :base(dasheng){}
    public overide void move()
    {
        Console.WriteLine("Swim as a fish....")
    }
}
public class Bird:DashengDecorator
{
    public Bird(Dasheng dasheng):base(dasheng){}
    public overide void fly()
    {
        Console.WriteLine("Fly as a bird");
    }
}

image-20230102181208045

image-20230102181534351

模式动机

  • 一般有两种方式可以实现给一个类或哦对象增加行为
    • 继承机制:使用继承机制是给仙有泪添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法,但是这种方法是静态的,用户不能控制增加行为的方式和时机
    • 关联机制,即将一个类对象嵌入到另一个对象中,由另一个对象来决定是否调用嵌入对象行为以便扩展自己的行为,我们成个嵌入的对象为装饰器
  • 装饰模式以对客户透明的方式动态的给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同,装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展,则就是装饰模式的模式动机

模式定义

装饰模式:动态的给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活,其别名也可以成为包装器,与适配器模式的别名相同,但他们适合用于不同的场合。根据翻译的不同,装饰模式也有人称之为油漆工模式,它是一种对象结构型模式

image-20230102182017897

模式结构

装饰模式包含如下角色

  • Component:抽象构件
  • ConcreateComponent:具体构件
  • Decorator:抽象装饰类
  • ConcreateDecorator:具体装饰类

image-20230102182113399

模式分析

  • 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性-,而且集成是一种耦合度较大的静态关系,无法在程序运行时动态扩展,在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关使系系统具有较好的松耦合性,因此使使得系统更加容易维护,当然,关联关系的缺点是比继承关系要创建更多的对象
  • 使用装饰模式来实现扩展比继承更加灵活,他以对客户透明的方式动态的给一个对象附加更多的责任,在装饰模式可以在不需要创建更多子类的情况下,将对象的功能加以扩展
典型的抽象装饰类代码
public class Decorator extends Component
{
    private Component component;
    public Decorator(Componnet component)
    {
        this.component=componnet;
    }
    public void operation()
    {
        component.operation();
    }
}
典型的具体装饰类代码
public class ConcreateDecorator extends Decorator
{
    public ConcreateDecorator(Component component)
    {
        super(component);
    }
    public void operation()
	{
    	super.operation();
    	addedBehavior();
	}
    public void addedBehaviour()
    {
        //新增办法
    }

}

实例:变形金刚

变形金刚在变形之前是一辆汽车,他可以在陆地上移动,当他变成机器人之后除了能够在陆地上移动之外,还可以说话,,如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔

image-20230102183339771

image-20230102184828396

装饰模式

public interface Transform
{
    public void move();
}
public finnal class Car implements Transform
{
    public Car(){
        sout("变形金刚是一辆车!")
    }
    public void move()
    {
        sout("在陆地上移动!")
    }
}
public class Changer implelments Transform
{
 	private Transform transform;
    public Changer(Transform transform)
    {
        this.transform=transform;
    }
    public void move()
    {
        transform.move();
    }
}

换衣小游戏,人还是那个人,不断给他换衣服,还可以一层套一层的

image-20230102185108925

Person类——被装饰者

Finery类——抽象装饰类

image-20230102185146003

person类--被装饰者类
public class person
{
    priate String name;
    public void setName(String name) {
        this.name = name;
    }
	public void show() {
        System.out.println("装扮的" + name);
    }

}
public abstract class Finery extends Person
{
    private Person person;
    public void setPerson(Person person)
    {
        this.person=person;
    }
    @Overide
    public void show()
    {
        if(person!=null)
        {
            person.show();
        }
    }
}
其他具體裝飾類
public class BigTrousers extends Finery
{
    @Overide
    public void show()
    {
        super.show();
        sout("垮褲");
    }
}
客戶端類
public class Client
{
    public static void main(String[] agrs)
    {
        Person person=new Person();
        person.setName("小明")
        Finney xz = new Suit();
        Finnery Id = new Tie();
        Finnery px = new LeatherShoes();
        xz.setPerson(person);
        id.setPerson(xz);
        px.setPerson(id);
        px.show();
    }
}

image-20230102185802867

### 装饰模式的优点

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以比继承更多的灵活性
  • 可以通过一种动态的方式来扩展一个对象的功能,可以创造出很多不同行为的组合可以使用多个具体装饰类 来装饰同一对象,得到功能更为强大的对象
  • ==具体构建类与具体装饰类可以独立变化。==用户可以根据需要增加新的具体构件类和具体装饰类,在使用时在对其进行组合,原有代码无需修改,符合开闭原则

装饰模式的缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于他们之间相互连接的方式有所不同,而不是他们的类或者属性值有所不同,同时还将产生很多具体装饰类,这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加容易出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐

模式适用环境

  • 在不影响刚想起她对象的情况下,以动态、透明的方式给单个对象添加职责
  • 需要动态的给一个对象增加功能,这些功能也可以动态地被撤销
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时,不能采用继承的情况主要有两类
    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长
    • 第二类是因为类定义不能继承(如finnal类)

模式应用

在java.swing包中,可以通过装饰模式动态给一些构件增加新的行为或改善其外观显示

如Jlist构件本身并不支持直接滚动,既没有滚动条,要创建可以滚动的列表,可以使用如下代码

JList list = new JList();
JScrollPane sp = new JScrollPane(list);

抽象装饰 :FileterInputStream

protected volatitle InputStream in;
protected FileInputStream(InputStream in)
{
    this.in=in;
}

角色分配

  • 抽象构件类:InputStream
  • 具体构件类:FileInputStream,ByteArrayInputSteam
  • 抽象装饰类:FileInputStream
  • 具体装饰类:BufferedInputStream, DataInputStream等

客户端使用

FileInputStream inFs = new FileInputSteam("temp/fileSrc.txt");
BufferedInputStream inBS = new BufferedInputStream(inFs);
//定义一个字节数组,用于存放缓冲数据
byte[] data = new byte[1034];
inBS.read(data);

模式扩展

装饰模式的简化,需要注意的问题

  • 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前还是装饰之后的对象都可以一致对待
  • 尽量保持具体构件类Component作为一个轻类,也就是说==不要把太多的逻辑放在具体构件类中,可以通过装饰类对其进行扩展
  • 如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类

装饰模式的简化

image-20230102193155096

模式扩展

透明装饰模式(多重加密系统)

在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类新和具体装饰类型,而应该全部声明为抽象构件类型

Cipher sc,cc,ac;
sc = new SimpleCipher();
cc = new ComplexCipher(sc);
ac = new AdvancedCipher(cc);

半透明装饰模式(变形金刚)

大多数装饰模式都是半透明的装饰模式,而不是完全透明的,即允许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法

Transform camaro;
camaro = new Car();
camaro.move();
Robot bumblebee = new Robot(camaro);
bumblebee.move();
bumblebee.say();

外观模式

image-20230102193556022

image-20230102193619151

image-20230102193628752

image-20230102193641302

人性化的医院

  • 接待员代替病人进行挂号,划价等
  • 病人只需要和接待员打交道

image-20230102193718084

模式动机

引入外观绝色之后,==用户需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,==从而降低了系统的耦合度

模式定义

外观模式:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式

image-20230102194009517

外观模式包含如下角色

  • Facade:外观角色
  • Subsystem:子系统角色

模式分析

  • 根据单一职责原则,在软件中讲一个系统化分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,他为子系统的访问提供一个简单而单一的接口
  • 外观模式也是迪米特法则的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度
  • 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端于子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道
  • 外观模式的目的在于降低系统的复杂程度
  • 外观模式在很大程度上提高了客户端使用的便捷性,使得客户端无需关心子系统的工作细节,通过外观角色即可调用相关功能。
典型的外观角色代码
public class Facade
{
    private SubSystemA obj1 = new SubSysremA();
    private SubsSystemB obj2 = new SubSystemB();
    private SubSystemC obj3 = new SubSystemC();
    public void method()
    {
        obj1.method();
        obj2.method();
        obj3.method();
    }
}

实例:电源总开关

现在考察一个电源总开关的例子,以便进一步说明外观模式,为了使用方便,一个电源总开关可以控制四盏灯,一个风扇,一台空调和一台电视机的启动和关闭,通过该电源总开关可以同时控制上述所有电器设备,实用外观模式设计该系统

image-20230102194758261

image-20230102195605000

public class GeneralSwithchFacade
{
    private Light lights[] = new Light[4];
    private Fan fan;
    private AirConditioner ac;
    private Television tv;
    public GeneralSwitchFacade()
    {
        lights[0] = new Light("左前");
        lights[0]=new Light("左前");
		lights[1]=new Light("右前");
		lights[2]=new Light("左后");
		lights[3]=new Light("右后");	
		fan=new Fan();
		ac=new AirConditioner();
		tv=new Television();

    }
    public void on()
    {
        lights[0].on();
        lights[1].on();
        lights[2].on();
        fan.on();
        ac.on();
        tv.on();
    }
    public void off()
	{
		lights[0].off();
		lights[1].off();
		lights[2].off();
		lights[3].off();
		fan.off();
		ac.off();
		tv.off();		
	}   

}
public class Fan
{
    public void on
    {
        sout("风扇打开!")}
    public void off
    {
        sout("风扇关闭!");
    }
}public class AirConditioner{
	public void on()	{
		System.out.println("空调打开!");
	}
	
	public void off()	{
		System.out.println("空调关闭!");
	}
}

public class AirConditioner{
	public void on()	{
		System.out.println("空调打开!");
	}
	
	public void off()	{
		System.out.println("空调关闭!");
	}
}
public class Light
{
	private String position;
	
	public Light(String position)
	{
		this.position=position;	
	}
	
	public void on()
	{
		System.out.println(this.position + "灯打开!");
	}
	
	public void off()
	{
		System.out.println(this.position + "灯关闭!");	
	}
}
public class Light
{
	private String position;
	
	public Light(String position)
	{
		this.position=position;	
	}
	
	public void on()
	{
		System.out.println(this.position + "灯打开!");
	}
	
	public void off()
	{
		System.out.println(this.position + "灯关闭!");	
	}
}

public class Client
{
	public static void main(String args[])
	{
		GeneralSwitchFacade gsf=new GeneralSwitchFacade();
		gsf.on();
		System.out.println("-----------------------");	
		gsf.off();
	}
}


实例:文件加密

  • 某系统需要提供一个文件加密模块,加密流程包括三个操作,分别是读取源文件、加密保存加密之后的文件,读取文件和保存文件使用流来实现,这三个操作相对独立,其业务代码封装在这三个不同的类中
  • 现在需要提供一个统一的加密外观类,用户可以直接使用该类加密外观类完成文件的读取、加密和保存三个操作,而不需要与每一个类进行交互,使用外观模式设计该加密模块

外观模式

image-20230102201857672

 class FileReader
 { public string Read(string fileNameSrc) {
     Console.Write("读取文件, 获取明文: ");
     FileStream fs = null;14. StringBuilder sb = new StringBuilder(); 
     try {fs = new FileStream(fileNameSrc, FileMode.Open);18. int data;
     while((data = fs.ReadByte())!= -1). {
         sb = sb.Append((char)data);}
          fs.Close(); Console.WriteLine(sb.ToString());
           } catch(FileNotFoundException e) 
     {Console.WriteLine("文件不存在! "); }
     catch(IOException e) {
         Console.WriteLine("文件操作错误! "); }
     return sb.ToString(); }}
 class CipherMachine {
     public string Encrypt(string plainText)
     {
         Console.Write("数据加密, 将明文转换为密文: ");
         string es = "";
         char[] chars = plainText.ToCharArray();
         foreach(char ch in chars){
             string c = (ch % 7).ToString();
             es += c;
         }
         Console.WriteLine(es);
         return es;
     }
 }.} 
class FileWriter {
    public void Write(string encryptStr,string fileNameDes)11. { 				Console.WriteLine("保存密文, 写入文件。 "); 
 	FileStream fs = null;
	try { fs = new FileStream(fileNameDes, FileMode.Create);
	byte[] str = Encoding.Default.GetBytes(encryptStr);
	fs.Write(str,0,str.Length);
  	fs.Flush();20. fs.Close(); }catch(FileNotFoundException e) 				{Console.WriteLine("文件不存在! "); 
	}catch(IOException e)
                                                                 { Console.WriteLine(e.Message);Console.WriteLine("文件操作错误! ");. }} }
class EncryptFacade {
    //维持对其他对象的引用
    private FileReader reader;
    private CipherMachine cipher;
    private FileWriter writer;
    public EncryptFacade(){
        reader = new FileReader();
        cipher = new CipherMachine();
        writer = new FileWriter();}
    //调用其他对象的业务方法
    public void FileEncrypt(string fileNameSrc, string fileNameDes){
        string plainStr = reader.Read(fileNameSrc);
        string encryptStr = cipher.Encrypt(plainStr); writer.Write(encryptStr, fileNameDes); }} 


class EncryptFacade{
    //维持对其他对象的引用
    private FileReader reader;
    private CipherMachine cipher;
    private FileWriter writer;
    public EncryptFacade()
    { reader = new FileReader();
     cipher = new CipherMachine();
     writer = new FileWriter(); }
    //调用其他对象的业务方法
    public void FileEncrypt(string fileNameSrc, string fileNameDes)
    {
        string plainStr = reader.Read(fileNameSrc);
        string encryptStr = cipher.Encrypt(plainStr); 					writer.Write(encryptStr, fileNameDes); }} 

image-20230102202529540

外观模式优点

  • 对客户屏蔽子系统组件,减少了客户处理的对象树木并使得子系统使用起来更加容易,通过引入外观模式,客户代码将变得很简答,与之关联的对象也很少
  • 实现子系统与客户之间的松耦合关系,这使得子系统的组件的变化不会影响到调用它的客户类,只需要调整外观类即可
  • 降低了大型软件系统中的编译依赖性,并简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统,一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象
  • 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类

外观模式缺点

  • 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端类的源代码,违背了开闭原则

模式使用环境

  • 当腰为一个复杂子系统提供一个简单接口时可以使用外观模式,该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统
  • 客户程序与多个子系统之间存在很大的依赖性,引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度

模式应用

外观模式应用于JDBC数据库操作

public class JDBCFacade
{
    private Connection = null;
    private Statement statemet= null;
    public void open(String driver,String jdbcUrl,String suerName,String password)
    {
        
    }
        public int executeUpdate(String sql) {
        ......
    }
    public ResultSet executeQuery(String sql) {
        ......
    }
    public void close() {
        ......
    }

}

session外观模式是是javaee框架中的应用

image-20230102203420522

模式扩展

一个系统有多个外观类

在外观模式中,通常只需要一个外观类,并且此外观类只有一个是里,换言之他是一个单例类,很多情况下为了节约系统资源,一般将外观类设计为单例类,当然这并不意味着整个系统里只有一个外观类在一个系统中可以涉及多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能

不要试图通过外观类为子系统增加新行为

不要通过继承一个外观类在子系统中假如新的新的行为,这种做法是错误的,外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子胸膛或增加新的子系统类实现,不能通过外观类来实现

外观模式与迪米特法则

外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内内部的对象的相互作用被外观对象所取代,外观类充当了客户类与子系统类之间的第三者,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到迪米特法则要求的一个强有力的武器

外观模式的引入

外观模式的最大的缺点在在于违背了开闭原则当增加新的在系统或者移出子系统时需要修改外观刘,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象同时通过修改配置文件来达到不修改源代码并更换外观类的目的

抽象外观类的引入

image-20230102204032646

代理模式

image-20230102204242092

模式动机

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

image-20230102204321708

image-20230102204324529

image-20230102204331731

  1. windows里面的快捷方式
  2. 猪八戒去找高翠兰结果是孙悟空变得,可以这样理解,把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类
  3. 买火车票不一定在火车站买,也可以去代售点
  4. 一张支票或银行存单是账户中资金的代理,支票在交易市场中用来代替现金,并提供对签发人账号上资金的控制
  5. spring aop

模式动机

通过引入一个新的对象,如小图片和远程代理对象来实现对真实对象的操作或者将新的对象作为真是对象的一个替身,这种实现机制即为代理模式通过引入代理对象来间接访问一个对象这就是代理模式的模式动机

代理模式:给某一个对象提供一个代理,并由代理对象控制原对象的引用,代理模式的应为叫做proxy,它是一种对象结构型模式

模式

代理模式包含如下角色

Subject:抽象主题角色

Proxy:代理主题角色

RealSubject:真实主题角色

image-20230102211550300

模式分析

代理模式是以结构图比较简单,一般可以简化为如下图所示,但是在现实中要复杂很多

image-20230102212045948

典型的代理类实现代码
public class Proxy implements Subject
{
    private RealSubject realSubject = new RealSubject();
    public void preRequest(){....}
    public void request()
    {
        preRequest();
        realSubject.request();
        postRequest();
    }
    public void postRequest(){...}
}

实例一:论坛权限控制代理,在一个论坛中已注册用户和游客的权限不同,已注册的用户有用发帖,修改自己的注册信息,修改自己的帖子等功能,而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。

在本实例中我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

image-20230102212408676

public interface AbstractPermission
{
	public void modifyUserInfo();
	public void viewNote();
	public void publishNote();
	public void modifyNote();
	public void setLevel(int level);
}
public class RealPermission implements AbstractPermission
{	
	public void modifyUserInfo()
	{
		System.out.println("修改用户信息!");
	}

	public void viewNote()
	{   }
	
	public void publishNote()
	{
		System.out.println("发布新帖!");
	}
	
	public void modifyNote()
	{
		System.out.println("修改发帖内容!");
	}
	
	public void setLevel(int level)
	{	}
}
public class PermissionProxy implements AbstractPermission
{
	private RealPermission permission=new RealPermission();
	private int level=0; 
		
	public void modifyUserInfo()
	{
		if(0==level)
		{
			System.out.println("对不起,你没有该权限!");
		}
		else if(1==level)
		{
			permission.modifyUserInfo();
		}
	}
	
	public void viewNote()
	{
		System.out.println("查看帖子!");
	}
		public void publishNote()
	{
		if(0==level)
		{
			System.out.println("对不起,你没有该权限!");
		}
		else if(1==level)
		{
			permission.publishNote();
		}		
	}
	
	public void modifyNote()
	{
		if(0==level)
		{
			System.out.println("对不起,你没有该权限!");
		}
		else if(1==level)
		{
			permission.modifyNote();
		}		
	}
	
	public void setLevel(int level)
	{
		this.level=level;
	}
}

public class Client
{
	public static void main(String args[])
	{
		AbstractPermission permission;
		permission=(AbstractPermission)XMLUtil.getBean();

		permission.modifyUserInfo();
		permission.viewNote();
		permission.publishNote();
		permission.modifyNote();
		System.out.println("----------------------------");
		permission.setLevel(1);
		permission.modifyUserInfo();
		permission.viewNote();
		permission.publishNote();
		permission.modifyNote();
	} 
}

image-20230102212741191

image-20230102212943326

public interface Image { void display(); }
public class RealImage implements Image { 
  private String fileName; 

  public RealImage(String fileName){ 
   this.fileName = fileName; 
   loadFromDisk(fileName); 
   } 

   @Override public void display()  { 
   System.out.println("Displaying " + fileName);
 } 

  private void loadFromDisk(String fileName){ 
   System.out.println("Loading " + fileName); }
 }
public class ProxyImage implements Image{ 

private RealImage realImage; private String fileName;

public ProxyImage(String fileName){ this.fileName = fileName; } 

@Override public void display()
{ if(realImage == null){ 
    realImage = new RealImage(fileName); }    
    realImage.display(); 
}

}
public class ProxyPatternDemo {
   public static void main(String[] args) { 
  Image image = new ProxyImage("test_10mb.jpg"); 
  // 图像将从磁盘加载 
  image.display(); 
  System.out.println(""); 
  // 图像不需要从磁盘加载 image.display(); 
   }
}



image-20230102213201921

public classProxyPatterDemo{

	public static void main(String[] args){
        Image image = new ProxyImage("test_10mb.jpg");
        //图像将从磁盘架子啊
        image.display();
        sout("");
        //图像不需要从磁盘加载
    }



}


image-20230102213415227

代理模式的优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
  • 远程代理使得客户端可以访问远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以及时那好系统资源的消耗,对系统进行优化并提高运行速度
  • 保护代理可以控制对真实对象的使用权限

代理模式的缺点

  • 由于在客户端和真实主体之间增加了代理对象,由此有些类型代理模式可能会造成请求的处理速度变慢
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂

模式使用环境

  • 远程代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同地址空间可以在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)
  • 虚拟代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才被会真正创建
  • Copy-on-write他是虚拟代理的一种,把复制克隆操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,copy-on-write代理可以让这个操作延迟,只有对象被用到的是后才被克隆
  • 保护代理:控制对一个对象的访问,可以给不通风的用户提供不同级别的使用权限
  • 缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
  • 防火墙代理:保护目标不让恶意用户接近
  • 同步化代理:使几个用户能够同时使用一个对象而没有冲突
  • 智能引用代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用次数记录下来等
  1. javaRMI(Remote Method Invocation,远程方法调用)image-20230102214254145

  2. EJB、WEB SERvice等分布式技术都是代理模式的应用,在EJBZHONG 使用了RMI机制,远程服务器中的企业级BEAN在本地有一个装袋里,客户端通过桩来调用远程对象中定义的方法,而无需直接与远程对象交互,在EJB的使用中需要提供一个公共的接口,客户端针对该接口进行编程,无需知道桩以及远程EJB的实现细节

  3. spring框架中的AOP技术也是代理模式的应用,在springaop中应用动态代理技术

几种常用的代理模式

  • 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制

  • 用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端,,当需要浏览大图片时,再将图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以在启动一个现线程来显示响应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览

  • 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在,客户完全可以认为被代理的远程业务对象是局域的而不是远程的。而远程代理对象承担了大部分的网络通信工作。

image-20230102215047138

  • 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了,虚拟代理模式是一种内存节省技术,安那些占用大量内存或处理复杂的对象将被推迟到使用它的时候才被创建

  • 在应用程序启动的时候,可以用代理对象代替真实对象初始化,节省了内存的占用,并大大加速了系统的启动时间

  • 动态代理

    • 动态代理是一种较为高级的代理模式springaop

    • 在传统的代理模式中,客户端通过proxy调用realsubject类的request方法,同时还在代理类中封装了其他方法,如preRequest和postRequest,可以处理一些其他问题

    • 如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性,如果一个真实主题角色必须对应一个代理主题角色,这将导致系统种类个数急剧增加,因此需要想办法减少系统中类的个数,此外,如何在实现不知道真实主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问题

    • java动态代理实现相关类位于java.lang.reflect,主要涉及两个类

      • invocationHandler接口,他是代理实例的调用处理程序实现的接口,该接口中定义了如下方法,public Object invoke(Object proxy, Method method, Object[] agrs)throws Throwable;invoke()方法中第一个参数proxy表示代理类,第二个参数method表示需要代理的方法,第三个参数表示代理方法的参数数组
      • proxy类,即为动态代理类,该类最常用的方法public static Obeject new ProxyInstance(Class loader,class<?>[] interfaces, InvocationHandler h)throws IleegalArgumentException new ProxyInstance()方法根据传入的接口类型interfaces返回一个动态创建的代理类的实例,方法中第一个参数loader表示代理类的类加载器,第二个参数interfaces表示代理类实现的接口列表(与真实主题类的接口列表一致)第三个参数h表示所指派的调用处理程序类
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
命令模式是一种行为型设计模式,用于将请求封装成一个对象,从而使得请求的发送者与接收者解耦。它将请求的操作封装在一个命令对象中,使得可以将不同的请求参数化,并且支持命令的撤销、恢复等操作。 命令模式的关键角色包括命令接口、具体命令、命令的发送者和命令的接收者。命令接口定义了执行命令的方法,具体命令实现了具体的命令逻辑,命令的发送者负责创建和发送命令对象,命令的接收者负责执行具体的命令操作。 命令模式的优点包括: 1. 解耦请求发送者和接收者:通过将请求封装在命令对象中,使得请求的发送者和接收者解耦,它们之间不需要直接通信。 2. 支持命令的撤销、恢复:由于命令对象封装了命令的操作,可以方便地实现命令的撤销、恢复等功能。 3. 支持扩展新的命令:通过新增具体命令类,可以方便地扩展新的命令,而无需修改现有的代码。 然而,命令模式也存在一些缺点: 1. 增加了类的数量:引入命令对象会增加额外的类,增加了代码的复杂性。 2. 可能导致系统过于臃肿:如果命令对象过多,可能会导致系统的类数量过多,增加维护和理解的难度。 总的来说,命令模式适用于需要将请求发送者和接收者解耦的情况下,能够提供一种统一的命令接口,使得请求的发送者不需要关心具体的命令逻辑。在需要支持命令的撤销、恢复等功能,或者需要扩展新的命令的场景下,命令模式是一个很好的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值