参考:尚硅谷,学校课件
适配器模式:
1)适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器
2) 适配器模式属于结构型模式
3) 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
类适配器:
示例代码:
//============= //Target接口,就是我们想把其他类的功能放到这个接口 public interface Robot { public void cry(); public void move(); } //===================== //别人的类,假装不懂怎么实现,相当于不适合咱们的原装插座,怎没能力把它改成合适的,只能看见插口(方法的名称,参数,返回值) public class Dog { public void wang() { System.out.println("狗汪汪叫!"); } public void run() { System.out.println("狗快快跑!"); } } //=============== //适配器登场了,这是类适配器,继承未知类 public class DogAdapter extends Dog implements Robot { public void cry() { System.out.print("机器人模仿:"); super.wang(); } public void move() { System.out.print("机器人模仿:"); super.run(); } } //=============== //用户端使用 public class Client { public static void main(String args[]) { Robot robot=new DogAdapter(); robot.cry(); robot.move(); } }
对象适配器:
代码分析:
//=============== //对象是配置器,与上面代码唯一不同就是适配器不继承未知类,而是把他的对象引用当成自身属性 public class Adapter extends Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee=adaptee; } public void request() { adaptee.specificRequest(); } }
接口适配器:
原理:通过抽象类来实现适配。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
桥接模式:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
情形分析:
对于毛笔而言,由于型号是其固有的维度,因此可以设计一个抽象的毛笔类,在该类中声明并部分实现毛笔的业务方法,而将各种型号的毛笔作为其子类;颜色是毛笔的另一个维度,由于它与毛笔之间存在一种“设置”的关系,因此我们可以提供一个抽象的颜色接口,而将具体的颜色作为实现该接口的子类。在此,型号可认为是毛笔的抽象部分,而颜色是毛笔的实现部分,见上图结构示意图。
现需要提供大中小3种型号的画笔,能够绘制5种不同颜色,如果使用蜡笔,我们需要准备3*5=15支蜡笔,也就是说必须准备15个具体的蜡笔类。而如果使用毛笔的话,只需要3种型号的毛笔,外加5个颜料盒,用3+5=8个类就可以实现15支蜡笔的功能。本实例使用桥接模式来模拟毛笔的使用过程。
示例代码:
//============ //这次我们先看使用时的样子 public class Client { public static void main(String a[]) { Color color;//是接口,有不同颜色的实现类,所谓实现部分 Pen pen;//是个抽象类,有大小类型的子类实现,所谓抽象部分 color=new Blue(); pen=new BigPen(); pen.setColor(color);//为毛笔选择颜料 pen.draw("鲜花");//使用毛笔 } } //============== //pen接口,里面有Color引用 public abstract class Pen { protected Color color; public void setColor(Color color) { this.color=color; } public abstract void draw(String name); } //================ //bigPen的实现,想在draw中不光体现"大号",也想体现颜色,所以在draw()方法实际调用内部的Color实现,这样能同时体现这两项内容 public class BigPen extends Pen { public void draw(String name) { String penType="大号毛笔绘制"; this.color.bepaint(penType,name); } } //============== //color的接口 public interface Color { void bepaint(String penType,String name); } //================== //Blue颜料的实现 public class Blue implements Color { public void bepaint(String penType,String name) { System.out.println(penType + "蓝色的"+ name + "."); } }
桥接模式注意事项和细节:
1) 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实 现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
2) 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部 分由具体业务来完成。
3) 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
4) 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层, 要求开发者针对抽象进行设计和编程
5) 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局 限性,即需要有这样的应用场景。
组合模式:
模式动机:
对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行。(递归调用)
由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下我们希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。
模式结构:
实例:水果盘
在水果盘(Plate)中有一些水果,如苹果(Apple)、香蕉(Banana)、梨子(Pear),当然大水果盘中还可以有小水果盘,现需要对盘中的水果进行遍历(吃),当然如果对一个水果盘执行“吃”方法,实际上就是吃其中的水果。使用组合模式模拟该场景。
代码结构示例如下:
分析上面的结构,各种水果和盘子都继承了MyElement对象,盘子有List<MyElement > list对象,盘子的add()接受的是MyElement接口类型,也就是可以盘子装水果,也可以盘子里套盘子。盘子的eat()方法,就是调用盘子里装的list元素的eat()方法,如果是水果直接吃,如果是盘子就吃里面的水果,这两种行为再外部来看都是MyElement.eat()
代码示例:
//==================== //MyElement,水果与盘子共同的方法 public abstract class MyElement { public abstract void eat(); } //=============== //水果比较简单,直接吃就可以了 public class Apple extends MyElement { public void eat() { System.out.println("吃苹果!"); } } //=============== //盘子除了吃(调用盘子装的元素),还要对盘中元素有基本的管理操作 public class Plate extends MyElement { private ArrayList list=new ArrayList(); public void add(MyElement element) { list.add(element); } public void delete(MyElement element) { list.remove(element); } public void eat() { for(Object object:list) { ((MyElement)object).eat(); } } } //==================== //站在用户端,吃就完事了 public class Client { public static void main(String a[]) { 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); plate1.eat(); } }
组合模式的注意事项和细节
1) 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子 的问题。
2) 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动.
3) 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点 或者叶子从而创建出复杂的树形结构
4) 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
5) 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性 都不一样,不适合使用组合模式
装饰模式:
模式动机:
在软件设计中,一般有两种方式可以实现给一个类或对象增加行为(新功能):
继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来调用嵌入对象的行为同时扩展其行为,我们称这另一个对象为装饰器(Decorator)。
装饰器使用对象之间的关联关系取代类之间的继承关系,在装饰器中既可以调用原有类的方法,还可以增加新的方法,以扩充原有类的功能。它通过一种无须定义子类的方式来给对象动态增加职责,符合合成复用原则。
分析下上图结构,很明显类似适配器,ConcreteComponent类似一个固定的类,只不过装饰模式下我们想的是对该类的功能进行增强,将装饰类和该类继承同一个抽象类,然后装饰类内部有父类引用(用来指向固定类),然后又装饰类的子类进行个性化定制。
实例:变形金刚
变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔。
结构解析:
代码示例:
//============== //要魔改的方法,用接口用抽象类都一样,,吧 public interface Transform { public void move(); } //=============== //开始固定的类,也就是基础小汽车形态 //Car()是创建的时候输出一下 public final class Car implements Transform { public Car() { System.out.println("变形金刚是一辆车!"); } public void move() { System.out.println("在陆地上移动!"); } } //====================== //这里的装饰器保留了move()原功能没有动,只是加入了额外的功能 public class Changer implements Transform { private Transform transform; public Changer(Transform transform) { this.transform=transform;//为了使用Car的方法 } public void move() { transform.move(); } } //=============== //变身小飞机,多加了一个fly()方法 public class Airplane extends Changer { public Airplane(Transform transform) { super(transform); System.out.println("变成飞机!"); } public void fly() { System.out.println("在天空飞翔!"); } } //================= //客户端使用 public class Client { public static void main(String args[]) { Transform camaro; camaro=new Car(); camaro.move(); System.out.println("-----------------------------"); Airplane bumblebee=new Airplane(camaro); bumblebee.move();//注意这里变成飞机还可以在地上跑 bumblebee.fly(); } }
外观模式:
在现实生活中,存在如下一些场景:
想组装一台新电脑,一个方案是去电子市场把自己需要的配件都买回来,然后自己组装,但需要对各种配件都要比较熟悉,这样才能选择最合适的配件,而且还要考虑配件之间的兼容性;另外一个方案,就是到电子市场,找一家专业装机的公司,把具体的要求一讲,然后就等着拿电脑就好了,方案二不需要挨家去跑,也不需要熟悉各种配件,通常是大多数人的选择。
我们把组装电脑的过程抽象一下:如果把电子市场看成是一个系统,而各个卖配件的公司看成是子系统的话,就类似于出现了这样一种情况:客户端为了完成某个功能,需要去调用某个系统中的多个子系统,简称为子系统A、子系统B和子系统C,对于客户端而言,那就需要知道A、B、C这三个子系统的功能,还需要知道如何组合A、B、C这三个子系统所提供的功能来实现自己所需要的功能,非常麻烦。
方案二提供了一种简单的方法,能让客户端实现相同的功能却不需要跟系统中的多个模块交互。
模式结构:
示例:电器总开关
就是把一些乱七八糟的小对象综合起来,放到一个GeneralSwitchFacade(遥控器)中,点下开关全部开启,关上开关全部关闭。
代码实现:
//================ //电扇 public class Fan { 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 GeneralSwitchFacade { private Light lights[]=new Light[4]; private Fan fan; private AirConditioner ac; private Television tv; public GeneralSwitchFacade() { 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(); lights[3].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 Client { public static void main(String args[]) { GeneralSwitchFacade gsf=new GeneralSwitchFacade(); gsf.on(); System.out.println("-----------------------"); gsf.off(); } }
外观模式的注意事项和细节
1) 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
2) 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
3) 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
4) 当系统需要进行分层设计时,可以考虑使用Facade模式
5) 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时 可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性
6) 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的。
享元模式:
模式动机:
现要开发一围棋软件,通过分析发现,围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大。如何实现对这些相同或者相似对象的共享访问,从而节约内存并提高系统性能?
享元模式正是为解决这一类问题而诞生的。享元模式通过共享技术实现相同或相似对象的重用。
再看看软件开发中常见的字符串,比如一个文本字符串中往往存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间。
这时就可以通过享元模式实现相同或相似对象的重用。在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例。
在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个享元对象,将其放在享元池中,需要时再从享元池取出。
享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。下面将对享元的内部状态和外部状态进行简单的介绍:
内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。如字符的内容,不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。
外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态注入享元对象中。
正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
经典享元模式分析:
Flyweight: 抽象享元类,通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
ConcreteFlyweight: 具体享元类,实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
UnsharedConcreteFlyweight: 非共享具体享元类,并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
FlyweightFactory: 享元工厂类,用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
享元模式的核心在于享元工厂类,在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
实例:共享网络设备
很多网络设备都是支持共享的,如交换机、集线器等,多台终端计算机可以连接同一台网络设备,并通过该网络设备进行数据转发,如图所示,现用享元模式模拟共享网络设备的设计原理。
虽然网络设备可以共享,但是分配给每一个终端计算机的端口(Port)是不同的,因此多台计算机虽然可以共享同一个网络设备,但必须使用不同的端口。我们可以将端口从网络设备中抽取出来作为外部状态,需要时再进行设置。
示例结构分析;
实现代码:
//================== //网络设别接口 //getType()返回是集线器还是交换机,因为这个接口被这两种设备继承,所以类型算内部状态,故没有参数也能得到 //use(Port port) 不管是集线器还是交换机,都要让计算机接入,还需要接口,但一个网络设备要供好多计算机使用,如果把port当做成员变量,需要List<Port> 好不好另说,本题的意思把Port当做外部状态,所以用作参数。 public interface NetworkDevice { public String getType(); public void use(Port port); } //=============== //port比较简单 public class Port { private String port; public Port(String port) { this.port=port; } public void setPort(String port) { this.port=port; } public String getPort() { return this.port; } } //=============== //交换机的实现 public class Switch implements NetworkDevice { private String type; public Switch(String type) { this.type=type; } public String getType() { return this.type; } public void use(Port port) { System.out.println("Linked by switch, type is " + this.type + ", port is " + port.getPort()); } } //============== //集线器 public class Hub implements NetworkDevice { private String type; public Hub(String type) { this.type=type; } public String getType() { return this.type; } public void use(Port port) { System.out.println("Linked by Hub, type is " + this.type + ", port is " + port.getPort()); } } //================== //最重要的网络设备工厂,它实现了享元模式,只实例化了两个网络设备,然后用这两种网络设备实现所有计算机的接入,并且标注端口号,还记录网络设备和计算机(没调用一次网络设备工厂表示有一个计算机接入)的个数 public class DeviceFactory { private ArrayList devices = new ArrayList(); private int totalTerminal=0; public DeviceFactory() { NetworkDevice nd1=new Switch("Cisco-WS-C2950-24"); devices.add(nd1); NetworkDevice nd2=new Hub("TP-LINK-HF8M"); devices.add(nd2); } public NetworkDevice getNetworkDevice(String type) { if(type.equalsIgnoreCase("cisco")) { totalTerminal++; return (NetworkDevice)devices.get(0); } else if(type.equalsIgnoreCase("tp")) { totalTerminal++; return (NetworkDevice)devices.get(1); } else { return null; } } public int getTotalDevice() { return devices.size(); } public int getTotalTerminal() { return totalTerminal; } } //================ //客户端的实例 public class Client { public static void main(String args[]) { NetworkDevice nd1,nd2,nd3,nd4,nd5; DeviceFactory df=new DeviceFactory(); nd1=df.getNetworkDevice("cisco"); nd1.use(new Port("1000")); nd2=df.getNetworkDevice("cisco"); nd2.use(new Port("1001")); nd3=df.getNetworkDevice("cisco"); nd3.use(new Port("1002")); nd4=df.getNetworkDevice("tp"); nd4.use(new Port("1003")); nd5=df.getNetworkDevice("tp"); nd5.use(new Port("1004")); System.out.println("Total Device:" + df.getTotalDevice()); System.out.println("Total Terminal:" + df.getTotalTerminal()); } }
享元模式的注意事项和细节
1) 在享元模式这样理解,“享”就表示共享,“元”表示对象
2) 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时, 我们就可以考虑选用享元模式
3) 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable存储
4) 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
5) 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有 固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
6) 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
7) 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
享元模式与单例模式的比较:
首先,享元模式解决的是减少大量小对象的内存开销,单例模式解决的是某个类的实例在程序中只需要一个。其次,享元模式和单例模式,虽然都是通过共享对象来解决,但是享元模式中的对象是有外部状态的,比如,在文本编辑软件中,对字符的处理,在文件中会出现大量相同的字母,相同这是对象的内部状态,而相同的字母在不同位置有时他的颜色、背景色等等,不一样,这是外部状态,通常外部状态是通过方法传递过来的。
在说下这两个模式的代码吧
代理模式通常使用懒汉方式
而享元模式,可以看做是单例模式+工厂模式+合成模式
原文:https://www.cnblogs.com/wangid3/p/14158642.html