设计模式(二)
约 7705 字大约 26 分钟
2017-05-27
结构性模式。
适配器模式
适配器模式(Adapter Pattern)将一个类的接口转换成客户端所希望的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
用电器做例子,笔记本电脑的插头一般都是三相的,即除了阳极、阴极外,还有一个地极。而有些地方的电源插座却只有两极,没有地极。电源插座与笔记本电脑的电源插头不匹配使得笔记本电脑无法使用。这时候一个三相到两相的转换器(适配器)就能解决此问题,而这正像是本模式所做的事情。
适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。
类的适配器模式
类的适配器模式把适配的类的API转换成为目标类的API。

在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,提供一个中间环节,即类Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的。

Target
public interface Target {
/**
* 这是源类Adaptee也有的方法
*/
public void sampleOperation1();
/**
* 这是源类Adapteee没有的方法
*/
public void sampleOperation2();
}上面给出的是目标角色的源代码,这个角色是以一个JAVA接口的形式实现的。可以看出,这个接口声明了两个方法:sampleOperation1()和sampleOperation2()。而源角色Adaptee是一个具体类,它有一个sampleOperation1()方法,但是没有sampleOperation2()方法。
Adaptee
public class Adaptee {
public void sampleOperation1(){}
}适配器角色Adapter扩展了Adaptee,同时又实现了目标(Target)接口。由于Adaptee没有提供sampleOperation2()方法,而目标接口又要求这个方法,因此适配器角色Adapter实现了这个方法。
Adapter
public class Adapter extends Adaptee implements Target {
/**
* 由于源类Adaptee没有方法sampleOperation2()
* 因此适配器补充上这个方法
*/
@Override
public void sampleOperation2() {
//写相关的代码
}
}对象的适配器模式
与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类

从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
Target
public interface Target {
/**
* 这是源类Adaptee也有的方法
*/
public void sampleOperation1();
/**
* 这是源类Adapteee没有的方法
*/
public void sampleOperation2();
}Adaptee
public class Adaptee {
public void sampleOperation1(){}
}Adapter
public class Adapter {
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
/**
* 源类Adaptee有方法sampleOperation1
* 因此适配器类直接委派即可
*/
public void sampleOperation1(){
this.adaptee.sampleOperation1();
}
/**
* 源类Adaptee没有方法sampleOperation2
* 因此由适配器类需要补充此方法
*/
public void sampleOperation2(){
//写相关的代码
}
}类适配器和对象适配器
●类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。
●对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理 Adaptee的子类了。
对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。
●对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。
对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。
●对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。
对于对象适配器,需要额外的引用来间接得到Adaptee。
建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。
缺省适配模式
缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。作为适配器模式的一个特例,缺省是适配模式在JAVA语言中有着特殊的应用。

public interface AbstractService {
public void serviceOperation1();
public int serviceOperation2();
public String serviceOperation3();
}public class ServiceAdapter implements AbstractService{
@Override
public void serviceOperation1() {
}
@Override
public int serviceOperation2() {
return 0;
}
@Override
public String serviceOperation3() {
return null;
}
}可以看到,接口AbstractService要求定义三个方法,分别是serviceOperation1()、serviceOperation2()、serviceOperation3();而抽象适配器类ServiceAdapter则为这三种方法都提供了平庸的实现。因此,任何继承自抽象类ServiceAdapter的具体类都可以选择它所需要的方法实现,而不必理会其他的不需要的方法。
适配器模式的用意是要改变源的接口,以便于目标接口相容。缺省适配的用意稍有不同,它是为了方便建立一个不平庸的适配器类而提供的一种平庸实现。
在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。
桥接模式
桥接模式的本质是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展,基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。
示例
一个咖啡店卖咖啡,卖出的咖啡可以从几个角度来看:
口味:摩卡、拿铁、美式等;
容量:大杯、中杯、小杯等;
添加物:牛奶、糖、蜂蜜等;
温度:热、常温、冷等;
如果需要设计一个点餐系统,来生成咖啡订单,如何在系统中将上面说到的拥有4个属性的咖啡生成呢?
第一种方法
为每种咖啡组合编写不同的类,如下
大杯热牛奶摩卡类;
中杯热牛奶摩卡类;
小杯热牛奶摩卡类;
大杯热蜂蜜摩卡类; ......
试想一下,需要多少各类?假设4个维度分别可选的值是a、b、c、d,那么需要定义的类就是a x b x c x d种。而且每种属性都是固化到特定的类,没法复用,按照这个方法要定义3x3x3x3=81个类。
第二种方法
针对第一种方法,我们能不能单独定义每种属性,并让每种属性能够复用,然后将他们组合/聚合起来形成一杯咖啡呢?这样就减少每个属性的重复定义了。 例如: 1. 口味:摩卡、拿铁、美式等分别定义一个类;3个类 2. 容量:大杯、中杯、小杯等分别定义一个类;3个类 3. 添加物:牛奶、糖、蜂蜜等分别定义一个类;3个类 4. 温度:热、常温、冷等分别定义一个类;3个类
如果能够找到一种方法把这4个维度组合起来,是不是总的类数就变成a+b+c+d=3+3+3+3=12个类?类的数量锐减。
那么如何将这个4个维度组合起来实现一杯咖啡呢?
桥接模式的原理
桥接模式就是为了实现上面的第二种方法的,先进行抽象,然后通过桥接将属性连接起来。
看一下下面原理图(初步思路):

再优化一下,进一步思路如下图:

想一下,咖啡的最重要的属性就是口味(也可以说是本质的属性),其它的属性相对来说不是最重要的,那么我们就让口味直接通过继承的方式实现属性的集成,其它三个属性为了提升替换性/重用性,可以也进行一个抽象类的定义,然后去具体实现。
这样设计后,通过组合/聚合实现了咖啡多个属性的集成,减少了类的数量。
图中组合/聚合关系就是桥接模式中Bridge的核心要义,通过组合/聚合将多个属性连接起来的。
最后就形成了桥接模式的通用示意图,如下:

**桥接模式的角色:
抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用,就是上面所说的Coffe抽象类。
修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义,就是上面的口味实现类。
实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。就是上面的容量、温度、添加物抽象类。
具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现,就是上面的容量、温度、添加物实现类。
- 抽象化(Abstraction)角色:Coffe抽象类-AbstractCoffe类的定义
public abstract class AbstractCoffe {
// 重点:此处就是连接其它属性的关键,通过成员变量的方式聚合了其它属性
ICapacity capacity;
IAdditives additives;
ITemperature temperature;
// 此处通过有参构造的方式接受客户端想要的咖啡属性
public AbstractCoffe(ICapacity capacity,IAdditives additives,ITemperature temperature){
this.capacity = capacity;
this.additives = additives;
this.temperature = temperature;
}
public abstract void Order(int count);
}
- 修正抽象化(RefinedAbstraction)角色:口味实现类-MocaCoffe类定义
public class MocaCoffe extends AbstractCoffe{
public MocaCoffe(ICapacity capacity,IAdditives additives,ITemperature temperature){
super(capacity, additives, temperature);
}
public void Order(int count) {
System.out.println("开始制作摩卡咖啡:");
capacity.SetCupCapacity();
additives.AddAdditives();
temperature.SetTemperature();
System.out.println(count+" 杯Moca Coffe制作完成!");
}
}
//本例中就实现了一种类型,就是摩卡,还可以实现其它的口味类型,代码类似,此处就省略了。
- 实现化(Implementor)角色:容量、温度、添加物抽象类-ICapacity、ITemperature、IAdditives抽象类定义
//(1)容量抽象类ICapacity
public interface ICapacity {
public void SetCupCapacity();
}//(2)温度抽象类ITemperature
public interface ITemperature {
public void SetTemperature();
}//(3)添加物抽象类IAdditives
public interface IAdditives {
public void AddAdditives();
}
- 具体实现化(ConcreteImplementor)角色:容量、温度、添加物实现类-Capacity、Temperature、Additives抽象类定义
/**
(1)Capacity实现类-3种
*/
// 大杯
public class LargeCapacity implements ICapacity{
@Override
public void SetCupCapacity() {
System.out.println("制作杯子尺寸:Large");
}
}
//中杯
public class MiddleCapacity implements ICapacity{
@Override
public void SetCupCapacity() {
System.out.println("制作杯子尺寸:Middle");
}
}
//小杯
public class SmallCapacity implements ICapacity{
@Override
public void SetCupCapacity() {
System.out.println("制作杯子尺寸:Small");
}
}/**
(2)Temperature实现类-3种
*/
// 热饮
public class HotTemperature implements ITemperature{
@Override
public void SetTemperature() {
System.out.println("加热温度至:Hot");
}
}
// 冷饮
public class NormalTemperature implements ITemperature{
@Override
public void SetTemperature() {
System.out.println("加热温度至:Normal");
}
}
// 常温
public class ColdTemperature implements ITemperature{
@Override
public void SetTemperature() {
System.out.println("加热温度至:Cold");
}
}/**
(3)Additives实现类-2种
*/
// 加奶
public class MilkAdditives implements IAdditives{
@Override
public void AddAdditives() {
System.out.println("添加 MILK 成功!");
}
}
// 加糖
public class SugarAdditives implements IAdditives{
@Override
public void AddAdditives() {
System.out.println("添加 SUGAR 成功!");
}
}
- 客户端类定义
public class Main {
public static void main(String[] args) {
// 客户端传入想要的咖啡各个属性的具体对象
AbstractCoffe mocaCoffe = new MocaCoffe(new MiddleCapacity(),new SugarAdditives(),new ColdTemperature());
mocaCoffe.Order(4);
}
}优点
抽象与实现分离,扩展能力强,符合开闭原则。
实现细节对客户透明。
减少了因为继承带来的类爆炸。
缺点
抽象难度大;
类之间的关系变得复杂。
适用场景
某个类有多个的维度的变化,如果用继承就会使项目变的臃肿,会产生许多的子类。
抽象的部分和实现的部分都应该可以扩展。
组合模式
组合模式(Composite Pattern)是一种结构型设计模式,允许你将对象组合成树形结构以表示“整体-部分”关系。组合模式使得客户端对单个对象和组合对象的使用具有一致性,从而可以将复杂的对象结构简化为一组简单的递归关系。
在组合模式中,对象被组织成树形结构,其中包括复合对象(组合)和叶子对象。复合对象可以包含多个叶子对象和其他复合对象,而叶子对象不能包含任何其他对象。这些对象具有相同的接口,客户端可以通过相同的方式使用它们。
组合模式通过以下几个组件实现:
- 抽象组件(Component):定义了组合对象和叶子对象的公共接口。
- 叶子组件(Leaf Component):表示组合中的叶子对象,不能包含任何子对象。
- 复合组件(Composite Component):表示组合中的复合对象,可以包含其他叶子对象和复合对象。
- 客户端(Client):使用组合对象的客户端,通过组件的公共接口与组合对象进行交互。
组合模式的优点包括:
- 简化客户端代码:客户端可以像处理单个对象一样处理组合对象,无需关心其内部结构和类型。
- 灵活性:可以动态地添加或删除组合对象,而无需更改现有代码。
- 可扩展性:可以通过添加新的叶子和复合对象扩展现有组合对象。
- 符合单一职责原则:组合对象只负责管理其子对象,而具体的对象操作则交由叶子对象或复合对象实现。
一个简单的组合模式示例是一个文件系统,其中目录可以包含其他目录和文件。在这种情况下,文件和目录都可以看作是组合对象,它们具有相同的接口,可以相互组合。这种结构可以通过组合模式实现,并将目录和文件表示为组合对象的复合和叶子。
在Java中实现的简单demo
1.定义抽象组件(Component)接口,它是组合对象和叶子对象的公共接口。
public interface Component {
void operation();
}2.定义叶子组件(Leaf Component)类,它表示组合中的叶子对象。
public class Leaf implements Component {
@Override
public void operation() {
// 叶子对象的具体操作
}
}3.定义复合组件(Composite Component)类,它表示组合中的复合对象,可以包含其他叶子对象和复合对象。
import java.util.ArrayList;
import java.util.List;
public class Composite implements Component {
private List<Component> children = new ArrayList<>();
@Override
public void operation() {
// 复合对象的具体操作
for (Component child : children) {
child.operation();
}
}
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
public Component getChild(int index) {
return children.get(index);
}
}4.定义客户端(Client)类,使用组合对象的客户端,通过组件的公共接口与组合对象进行交互。
public class Client {
public static void main(String[] args) {
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
Composite composite1 = new Composite();
composite1.add(leaf1);
composite1.add(leaf2);
Component leaf3 = new Leaf();
Component leaf4 = new Leaf();
Composite composite2 = new Composite();
composite2.add(leaf3);
composite2.add(leaf4);
composite2.add(composite1);
composite2.operation();
}
}在这个例子中,我们创建了一个复合对象,它包含两个叶子对象和一个嵌套的复合对象。客户端代码可以像处理单个对象一样处理组合对象,而无需关心其内部结构和类型。在这个例子中,我们通过调用
composite2.operation()方法,递归调用了复合对象和叶子对象的operation()方法。
使用场景
- 当你需要表示对象的整体-部分层次结构时,可以使用组合模式。例如,文件系统中的目录和文件,GUI中的窗口和控件,组织结构图中的部门和员工等。
- 当你希望客户端能够统一处理复合对象和叶子对象时,可以使用组合模式。例如,你希望像处理单个对象一样处理组合对象,而无需区分它们的内部结构和类型。
- 当你需要以递归方式遍历复合对象中的所有元素时,可以使用组合模式。例如,你希望对文件系统中的目录和文件进行递归遍历,或对组织结构图中的部门和员工进行递归遍历。
- 当你需要添加或删除组合对象中的子元素时,可以使用组合模式。例如,你需要在文件系统中添加或删除目录和文件,或在组织结构图中添加或删除部门和员工。
总之,组合模式适用于需要表示对象的整体-部分层次结构,并希望以统一的方式处理复合对象和叶子对象的场景。它可以使得代码更加灵活、易于扩展,并且符合面向对象设计的开闭原则。
装饰器模式
装饰器模式(Decorator Pattern)属于设计模式中的结构型模式,它是作为现有的类的一个包装。实现方式一般是创建一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。在想要增强某个对象的功能但是又不想改变该对象的代码时,可以引入装饰器模式。

抽象组件(Component):定义了原始对象和装饰器对象的共同接口,可以是抽象类或接口。
具体组件(ConcreteComponent):实现了抽象组件接口,是需要被装饰的原始对象。
抽象装饰器(Decorator):继承了抽象组件,持有一个抽象组件对象的引用,并定义了与抽象组件相同的接口。
具体装饰器(ConcreteDecorator):继承了抽象装饰器,通过对抽象组件进行装饰,添加额外的功能。
Component
public interface Coffee {
void drink();
}ConcreteComponent
public class Cappuccino implements Coffee {
@Override
public void drink() {
System.out.println("上一杯卡布奇诺!");
}
}Decorator
public abstract class Decorator implements Coffee {
Coffee coffee;
public Decorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public void drink() {
coffee.drink();
}
}ConcreteDecorator
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Coffee coffee) {
super(coffee);
}
@Override
public void drink() {
System.out.println("上一杯加糖后的卡布奇诺!");
}
}测试
public static void main(String[] args) {
//原对象
Coffee cappuccino = new Cappuccino();
cappuccino.drink();
//装饰对象
Coffee decorator = new ConcreteDecorator(cappuccino);
decorator.drink();
}外观模式
为子系统的接口提供一组统一的入口。外观模式定义了一个高层接口,这个接口使得子系统的更加容易使用。
在外观模式中,一个子系统的外部与其内部通信通过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多其他对象交互。
外观模式是迪米特法则的一种具体实现,通过引入一个外观角色降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
迪米特法则: 每一个软件单元对其他单元都只有最少的信息,而且局限于那些与本单元密切相关的软件单元。迪米特法则要求一个软件实体应该尽可能少地与其他实体发生相互作用。

一个典型的用户与一个系统(汽车)中的多个子系统(动力系统,离合器,变速器,油门)进行交互的情形,用户需要和所有的子系统交互,才能完成自己想要实现的功能,这其实是极不合理的,也极容易出错。
首先来看看各个子系统的定义,包括:动力系统、离合器、加速器、变速器四个子系统。
/**
* 动力系统
*/
public class PowerSystem {
/**
* 汽车发动
*/
public void startUp() {
System.out.println("汽车发动。。。。");
}
/**
* 汽车熄火
*/
public void closeDown() {
System.out.println("汽车熄火。。。。");
}
}/**
* 离合器
*/
public class ClutchSystem {
/**
* 踩下离合
*/
public void press() {
System.out.println("踩下离合。。。。");
}
/**
* 松开离合
*/
public void release() {
System.out.println("松开离合。。。。");
}
}/**
* 变速器
*/
public class TransmissionSystem {
/**
* 挂挡操作
* @param gear 所挂档位
*/
public void shift(int gear) {
switch (gear) {
case -1:
System.out.println("挂倒档。。。。");
break;
case 0:
System.out.println("挂空档。。。。");
break;
case 1:
System.out.println("挂一档。。。。");
break;
case 2:
System.out.println("挂二档。。。。");
break;
case 3:
System.out.println("挂三档。。。。");
break;
case 4:
System.out.println("挂四档。。。。");
break;
case 5:
System.out.println("挂五档。。。。");
break;
}
}
}/**
* 加速器,即油门
*/
public class AcceleratorSystem {
/**
* 踩下油门
*/
public void press() {
System.out.println("踩下油门。。。。");
}
/**
* 松开油门
*/
public void release() {
System.out.println("松开油门。。。。");
}
}接下来该看看外观的定义了
/**
* 外观类
*/
public class Facade {
/**
* 示意方法,停车起步
*/
public void parkingStart() {
// 创建需要转调的子系统对象实例
ClutchSystem clutchSystem = new ClutchSystem();
TransmissionSystem transmissionSystem = new TransmissionSystem();
AcceleratorSystem acceleratorSystem = new AcceleratorSystem();
// 转调子系统的功能
clutchSystem.press();
transmissionSystem.shift(1);
clutchSystem.release();
acceleratorSystem.press();
System.out.println("汽车开始动了。。。。");
}
}创建一个客户端类测试一下,示例代码如下。
public class Client {
public static void main(String[] args) {
PowerSystem powerSystem = new PowerSystem();
// 发动汽车
// 此处作为示意,用户可以跳过外观,直接与子系统进行交互
powerSystem.startUp();
// 创建外观实例
Facade facade = new Facade();
// 停车起步
facade.parkingStart();
}
}
/*
并未把“发动汽车”这个步骤一并加入的Facade对象中,主要是为了作一个示意:根据实际需要,用户是可以越过Facade层,直接与子系统进行交互的。
*/输出如下:
汽车发动。。。。
踩下离合。。。。
挂一档。。。。
松开离合。。。。
踩下油门。。。。
汽车开始动了。。。。抽象外观类
在上述外观模式中,如果需要增加或删除与外观类交互的子系统类,必须修改原有代码,**违背开闭原则**,因此可以通过引入**抽象外观类**对系统进行改进。

引入抽象外观类之后,客户端可以针对抽象层编程,对于新的业务需求,不需要修改原有的外观类,直接增加一个新的具体外观类即可,符合开闭原则。
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在通过共享对象来最大限度地减少内存使用和提高性能。
>享元模式的核心思想是将对象的状态(内部数据)分为内部状态(Intrinsic State)和外部状态(Extrinsic State)。内部状态是对象可以共享的状态,它独立于对象的场景,可以被多个对象共享。外部状态是对象特定于场景的状态,它不可共享,每个对象需要自己管理。 > >享元模式的关键是引入一个享元工厂(Flyweight Factory),它负责创建和管理享元对象。当客户端需要对象时,它首先向享元工厂请求对象。如果对象已存在,享元工厂直接返回现有对象;如果对象不存在,享元工厂创建一个新的对象,并在需要时进行共享。以下是享元模式的组成部分:
- 享元接口(Flyweight):定义共享对象的接口,包括操作共享状态的方法。
- 具体享元(Concrete Flyweight):实现享元接口,并存储内部状态。
- 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保对象的共享和复用。
下面是一个简单的 Java 代码示例,演示了如何使用享元模式来共享数字对象:
import java.util.HashMap;
import java.util.Map;
// Flyweight 接口
interface Number {
void printNumber();
}
// ConcreteFlyweight 类
class ConcreteNumber implements Number {
private int value;
public ConcreteNumber(int value) {
this.value = value;
}
@Override
public void printNumber() {
System.out.println("Printing number: " + value);
}
}
// FlyweightFactory 类
class NumberFactory {
private static final Map<Integer, Number> numberMap = new HashMap<>();
public static Number getNumber(int value) {
if (!numberMap.containsKey(value)) {
numberMap.put(value, new ConcreteNumber(value));
}
return numberMap.get(value);
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 获取共享的数字对象
Number number1 = NumberFactory.getNumber(5);
Number number2 = NumberFactory.getNumber(10);
// 输出结果
number1.printNumber(); // 输出:Printing number: 5
number2.printNumber(); // 输出:Printing number: 10
}
}在这个示例中,NumberFactory 充当享元工厂的角色,负责创建和管理数字对象。当客户端请求一个数字时,工厂首先检查对象是否已存在,如果存在则返回已有的对象,如果不存在则创建一个新的对象并添加到工厂中。这样就实现了数字对象的共享,避免了重复创建,节省了内存资源。
享元模式是一种非常有用的设计模式,可以在系统中存在大量相似对象时节省内存资源。通过共享对象的状态,可以显著减少对象的数量,提高系统的性能和效率。在实际开发中,我们可以根据具体的需求来应用享元模式,从而优化系统的设计和实现。
代理模式
代理模式是一种结构型设计模式,其主要目的是为其他对象提供一种代理以控制对这个对象的访问。代理对象通常充当客户端和实际对象之间的中介,它可以在访问实际对象之前或之后执行一些额外的操作,例如权限控制、延迟加载、缓存等。
代理模式包含以下关键角色:
- Subject(抽象主题):定义了代理对象和真实对象的公共接口,客户端通过这个接口访问真实对象和代理对象。
- RealSubject(真实主题):定义了真实对象,是代理对象所代表的真实内容。
- Proxy(代理):保存了一个引用,使得代理可以访问实际主题,同时提供了与主题相同的接口,客户端通过代理访问真实主题。
下面是一个简单的 Java 代码示例,演示了如何使用代理模式来控制对实际对象的访问,并在访问前后执行额外的操作:
// Subject 接口
interface Image {
void display();
}
// RealSubject 类
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 + " from disk");
}
}
// Proxy 类
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 Main {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
// 图像将从磁盘加载
image.display();
System.out.println();
// 图像将不会从磁盘加载
image.display();
}
}在这个示例中,Image 接口定义了图片展示的公共方法 display()。RealImage 类表示真实的图片对象,而 ProxyImage 类充当了代理,负责控制对真实图片对象的访问。当客户端首次调用 display() 方法时,ProxyImage 会创建一个真实图片对象,并调用其 display() 方法展示图片。之后再次调用 display() 方法时,由于已经创建了真实图片对象,代理将直接调用真实图片对象的 display() 方法展示图片。
再来看一个关于缓存的示例,缓存是指将经常使用的数据存储在临时存储区域中,以加快数据访问速度。通过代理模式,我们可以在代理类中添加缓存逻辑,然后在访问真实对象之前先检查缓存是否存在。下面是一个简单的示例:
import java.util.HashMap;
import java.util.Map;
// 接口:数据访问
interface DataAccessor {
String getData(String key);
}
// 具体实现类:真实数据访问
class RealDataAccessor implements DataAccessor {
@Override
public String getData(String key) {
System.out.println("Fetching data from database for key: " + key);
return "Data for " + key;
}
}
// 代理类:缓存代理
class CachingProxy implements DataAccessor {
private DataAccessor realDataAccessor;
private Map<String, String> cache;
public CachingProxy() {
realDataAccessor = new RealDataAccessor();
cache = new HashMap<>();
}
@Override
public String getData(String key) {
if (cache.containsKey(key)) {
System.out.println("Retrieving data from cache for key: " + key);
return cache.get(key);
} else {
String data = realDataAccessor.getData(key);
cache.put(key, data);
return data;
}
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建缓存代理
DataAccessor dataAccessor = new CachingProxy();
// 第一次访问时从数据库获取数据,并缓存起来
System.out.println(dataAccessor.getData("key1"));
// 第二次访问时直接从缓存获取数据
System.out.println(dataAccessor.getData("key1"));
}
}在这个示例中,CachingProxy 是缓存代理类,它在访问真实对象之前先检查缓存是否存在数据。
通过代理模式,我们可以很容易地实现延迟加载、权限控制、缓存等功能,并且不需要修改原始对象的代码,从而增强了代码的可维护性和灵活性。