二、结构型模式
这类模式主要处理类或对象的组合,涉及如何组合类和对象以获得更大的结构。
包括:
- 适配器模式(Adapter)
- 装饰器模式(Decorator)
- 代理模式(Proxy)
- 外观模式(Facade)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 享元模式(Flyweight)
1、代理模式(Proxy)
1.1.代码实现
java
//操作步骤一:主题接口
public interface Subject {
void request();
}
//操作步骤二:一个实现Subject接口的类SubjectImpl
public class SubjectImpl implements Subject {
@Override
public void request() {
System.out.println("真实主题的请求");
}
}
//操作步骤三:一个实现Subject接口的代理类SubjectProxy
public class SubjectProxy implements Subject {
SubjectImpl subjectImpl;
@Override
public void request() {
if(subjectImpl==null){
subjectImpl=new SubjectImpl();
}
preRequest(); //1.预处理
subjectImpl.request(); //2.真实主题处理
postRequest(); //2.后处理
}
/**
* 代理类的预处理
* */
private void preRequest() {
System.out.println("代理类的预处理");
}
/**
*代理类的后处理
* */
private void postRequest() {
System.out.println("代理类的后处理");
}
}
//代码测试
public class ProxyTest {
/**
* 代理模式(AOP):
* 代理模式是一种设计模式,它通过创建一个代理对象来控制对原始对象的访问。以下是一个简单的Java代理模式示例:
* 1.创建一个接口Subject:
* 2.创建一个实现Subject接口的类SubjectImpl:
* 3.创建一个实现Subject接口的代理类SubjectProxy
* 4.最后,我们在main方法中使用代理类Proxy来调用真实主题的方法:
*/
public static void main(String[] args) {
Subject proxy = new SubjectProxy();
proxy.request();
}
}
1.2.概念总结
1.2.1.代理模式的作用:
-
**控制访问:**代理模式可以控制对原始对象的访问,例如可以在访问对象之前或之后执行一些额外的操作,如权限检查、日志记录等。
-
**扩展功能:**代理模式可以在不修改原始对象的基础上,通过代理类来扩展对象的功能。这意味着可以在不影响原有代码的情况下,为对象添加新的行为或特性。
-
**智能化处理:**代理能够实现智能化处理,比如动态代理可以在运行时动态地生成代理对象,这为处理一些需要在运行时才能确定具体行为的情况提供了便利。
-
**职责清晰:**在代理模式中,代理对象和真实对象的职责是分开的,这样可以使得代码的结构更加清晰,各个部分的职责更加明确。
-
**高扩展性:**由于代理模式基于接口或抽象类进行编程,只要实现了相应的接口,就可以被代理,这使得系统具有很高的扩展性。
1.2.2.代理模式常用于以下场景:
-
**远程代理:**为远程对象提供局部代表,以减少网络通信的开销。
-
**虚拟代理:**根据需要创建开销大的对象,通过代理来实现对象的延迟加载。
-
**安全代理:**用来控制不同权限的对象对原对象的访问。
-
**智能指引:**当对象无法自行决定如何处理时,由代理来决定。
-
**懒加载代理:**在需要时才加载实际对象,以提高性能。
总的来说,代理模式是一种非常实用的设计模式,它通过引入一个中介层,即代理对象,来控制和增强对真实对象的访问。这种模式在软件设计中广泛应用,尤其是在需要对对象的访问进行控制或扩展其功能时。
2、适配器模式(Adapter)
2.1.代码实现
java
//操作步骤一:目标接口
public interface Target {
void request();
}
//操作步骤二:适配器类
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
//操作步骤三:适配者类
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specificRequest");
}
}
//代码测试
public class AdapterTest {
public static void main(String[] args) {
//1.适配者类
Adaptee adaptee = new Adaptee();
//2.适配器类
Target target = new Adapter(adaptee);
//3.目标接口
target.request(); // 输出:Adaptee's specificRequest
}
}
2.2.概念总结
适配器模式是一种结构型设计模式,它通过将一个接口转换成客户期望的另一个接口,使得原本不兼容的两个类能够协同工作。
2.2.1.适配器模式的核心在于解决两个不同接口之间的兼容性问题。这种模式通常涉及到以下角色:
- **目标(Target)接口:**这是客户端所期待的接口,定义了客户端希望使用的方法和功能。
- **适配者(Adaptee)类:**这是一个现有的类,其接口与客户期望的目标接口不兼容。
- **适配器(Adapter)类:**这个类实现了目标接口,并且持有一个对适配者类的引用。它通过调用适配者的方法来实现目标接口的功能。
在实际应用中,适配器模式可以有几种不同的实现方式,包括类适配器、对象适配器和接口适配器。这些实现方式各有特点,但核心目的相同,即提供一种机制来协调不兼容的接口之间的差异。
2.2.2.适配器模式的优点包括:
- **复用性:**它可以让现有的类(适配者)与新的接口(目标)协同工作,从而重用已有的代码。
- **灵活性:**可以在不修改原有代码的基础上增加新功能。
- **解耦:**由于适配器作为中间层,它减少了系统各部分之间的直接依赖。
2.2.3.适配器模式缺点:
- **系统复杂性:**引入适配器可能会增加系统的复杂性,因为需要额外的类来处理接口转换。
- **性能开销:**调用适配器可能会引入一定的性能开销,特别是在频繁调用的场景下。
总的来说,适配器模式是一种非常有用的设计模式,它通过提供一个中间层来解决不同接口之间的兼容性问题,从而使得系统更加灵活和可扩展。
3、装饰者模式(Decorate)
3.1.代码实现
java
//操作步骤一:定义组件接口
public interface Component {
void operation();
}
//操作步骤二:实现组件接口的具体类
public class ConcreteComponent implements Component{
@Override
public void operation() {
System.out.println("执行原始操作");
}
}
//操作步骤三:定义装饰者抽象类,继承自组件接口
public abstract class Decorator implements Component{
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
//操作步骤四:实现具体装饰者类
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
public void operation() {
System.out.println("执行装饰者A的操作");
super.operation();
}
}
//操作步骤四:实现具体装饰者类
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
public void operation() {
System.out.println("执行装饰者B的操作");
super.operation();
}
}
//测试代码
public class DecoratorTest {
public static void main(String[] args) {
//1.创建原始对象
Component component = new ConcreteComponent();
//2.创建装饰者对象,并传入原始对象
Decorator decoratorA = new ConcreteDecoratorA(component);
Decorator decoratorB = new ConcreteDecoratorB(decoratorA);
//3.执行装饰后的操作
decoratorB.operation();
}
}
3.2.概念总结
装饰者模式是一种结构型设计模式,它允许在不改变对象原有结构的情况下,动态地添加功能和责任。
装饰者模式的核心思想是使用组合而非继承来扩展对象的功能。这种模式通过创建一个包装对象,即"装饰者",来包裹原始对象,从而在不修改原始对象的代码的情况下,为其添加新的行为或职责。这样做的好处是可以实现高度的灵活性和可扩展性,同时遵循了开闭原则(对扩展开放,对修改关闭)。
在Java中,装饰者模式通常使用接口或抽象类来实现,确保所有组件(包括原始对象和装饰者)都实现相同的接口或继承自相同的抽象类。这样,装饰者类可以反映出被装饰组件的类型,使得客户端可以统一处理原始对象和装饰后的对象。
3.2.1装饰者模式的一些关键特点:
- 透明性:装饰者模式应该对客户端透明,即客户端无需区分原始对象和装饰后的对象,它们应当具有相同的接口或类型。
- 动态性:装饰者可以在运行时动态地添加到对象上,这意味着可以根据需要随时增加或减少对象的职责。
- 灵活性:与继承相比,装饰者模式提供了更大的灵活性,因为它允许在不改变现有类的基础上添加新功能。
- 替代继承:当不适合使用子类进行扩展时,装饰者模式可以作为一种替代方法,以避免类层次过于复杂。
总的来说,装饰者模式适用于那些需要为对象动态添加功能的场景,尤其是在不希望通过继承来扩展类的情况下。
4、外观模式(Facade)
4.1.代码实现
java
//操作步骤一:子系统A
public class SubsystemA {
public void operationA() {
System.out.println("Subsystem A operation");
}
}
//操作步骤一:子系统B
public class SubsystemB {
public void operationB() {
System.out.println("Subsystem B operation");
}
}
//操作步骤一:子系统C
public class SubsystemC {
public void operationC() {
System.out.println("Subsystem C operation");
}
}
//操作步骤二:外观类
public class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
private SubsystemC subsystemC;
public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
subsystemC = new SubsystemC();
}
public void operation() {
subsystemA.operationA();
subsystemB.operationB();
subsystemC.operationC();
}
}
//测试代码
public class FacadeTest {
/**
* 1.定义了三个子系统SubsystemA、SubsystemB和SubsystemC,它们分别实现了各自的操作。
* 2.然后,我们创建了一个外观类Facade,它封装了对这三个子系统的访问,
并提供了一个统一的接口operation()来执行这些操作。
* 3.最后,在客户端代码中,我们通过创建Facade对象并调用其operation()方法来简化对子系统的访问。
*
* 这样,客户端只需要与外观类交互,而不需要直接与子系统进行交互,从而降低了系统的复杂性。
* 同时,如果需要添加新的子系统或修改现有子系统的操作,只需修改外观类即可,而无需修改客户端代码。
* */
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation();
}
}
4.2.概念总结
外观模式是一种结构型设计模式,用于简化多个子系统之间的复杂交互,并为客户端提供一个统一的接口来访问这些子系统。
外观模式的核心思想是创建一个高层接口,这个接口可以隐藏系统的内部工作细节,使得子系统更容易被使用。通过引入一个外观类,它为复杂的子系统调用提供了一个统一的入口,从而降低了子系统与客户端之间的耦合度。
4.2.1.外观模式的优点:
- 降低复杂性:通过提供一个简单的接口,外观模式帮助客户端屏蔽了子系统的复杂性。
- 松耦合:客户端不直接与子系统交互,从而减少了它们之间的依赖关系。
- 易于扩展:新的子系统可以很容易地添加到外观之后,而不需要修改现有的客户端代码。
然而,外观模式也有一些缺点,比如可能会增加系统的抽象层次,有时候可能会导致不必要的间接层。
4.2.2.外观模式适用于以下情况:
- 当需要为复杂的子系统提供一个简单接口时。
- 当客户端不应该直接访问子系统内部的类时。
- 当需要简化并统一多个子系统的操作时。
综上所述,外观模式是一种有效的设计模式,用于简化复杂系统的接口并提供一个统一的访问点,从而提高系统的可维护性和可扩展性。
5、桥接模式(Bridge)
5.1.代码实现
java
//操作步骤一:接口:绘图API
public interface DrawAPI {
void drawCircle(int radius, int x, int y);
void drawSquare(int side, int x, int y);
}
//操作步骤二:抽象类:形状
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
//操作步骤三:实现类:正方形
public class Square extends Shape {
private int x, y, side;
public Square(int x, int y, int side, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.side = side;
}
@Override
public void draw() {
drawAPI.drawSquare(side, x, y);
}
}
//操作步骤四:实现类:圆形
public class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius, x, y);
}
}
//操作步骤五:实现类:Windows API
public class WindowsDrawAPI implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", y: " + y + "]");
}
@Override
public void drawSquare(int side, int x, int y) {
System.out.println("Drawing Square[ color: blue, side: " + side + ", x: " + x + ", y: " + y + "]");
}
}
//操作步骤六:实现类:Mac OS X API
public class MacOSXDrawAPI implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", y: " + y + "]");
}
@Override
public void drawSquare(int side, int x, int y) {
System.out.println("Drawing Square[ color: yellow, side: " + side + ", x: " + x + ", y: " + y + "]");
}
}
//测试代码
public class BridgeTest {
public static void main(String[] args) {
Shape circle = new Circle(100, 100, 10, new WindowsDrawAPI());
Shape square = new Square(100, 100, 20, new MacOSXDrawAPI());
circle.draw();
square.draw();
}
}
5.2.概念总结
桥接模式是一种设计模式,旨在将抽象与实现分离开来,使得两者可以独立变化。这种模式属于对象结构型模式。具体来说,桥接模式有以下要点:
- 分离抽象和实现:它允许开发者将一个抽象类的抽象性与其具体实现分开处理。这样做的目的是为了避免类层次爆炸,当一个类无法同时满足多个维度的分类时尤其有用。
- 增加灵活性:通过桥接模式,可以在两个或多个独立变化的维度之间增加灵活性。例如,在图形应用程序中,形状(如圆形、正方形)和颜色(如红、蓝)可以独立变化,使用桥接模式可以将它们的变化解耦。
- 减少子类的生成:在不使用桥接模式的情况下,可能需要为每种可能的组合创建子类,这将导致类的数量急剧增加。桥接模式通过提供一个桥梁结构来连接不同的部分,从而避免了大量子类的产生。
- 适用于特定场景:当系统需要跨多个独立变化的维度进行扩展时,或者当一个类存在多个维度的分类且这些分类应该被独立地变化和扩展时,桥接模式是一个合适的选择。
- 示例应用:桥接模式可以应用于多种业务场景,如软件框架设计、工具库开发等。以星巴克订单系统为例,可以使用桥接模式将订单的创建过程与支付方式分离,使得新增支付方式或订单类型时不需要修改现有代码。
总的来说,桥接模式是一种强大的设计工具,它通过分离抽象与实现,提供了一种优雅的方式来管理类之间的复杂关系。在实际应用中,正确使用桥接模式可以极大地提高系统的灵活性和可维护性。
6、组合模式(Composite)
6.1.代码实现
java
//操作步骤一:组件接口
public interface Component {
void operation();
}
//操作步骤二:叶子节点
public class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Leaf " + name + " operation.");
}
}
//操作步骤三:组合对象
public class Composite implements Component {
private String name;
private List<Component> children = new ArrayList<>();
public Composite(String name) {
this.name = name;
}
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
System.out.println("Composite " + name + " operation.");
for (Component component : children) {
component.operation();
}
}
}
//测试代码
public class CompositeTest {
/**
* Component是一个接口,Leaf是组成该树的叶子节点,而Composite是组合节点,
它可以包含其他的叶子节点或者组合节点。
* main方法中创建了一个组合结构的树,并展示了如何执行根节点的操作,它会递归地调用所有子节点的操作。
* */
public static void main(String[] args) {
Composite root = new Composite("Root");
root.add(new Leaf("Leaf A"));
root.add(new Leaf("Leaf B"));
Composite branch = new Composite("Branch");
branch.add(new Leaf("Leaf C"));
branch.add(new Leaf("Leaf D"));
root.add(branch);
root.operation();
}
}
6.2.概念总结
组合模式是一种结构型设计模式,它允许将对象组合成树形结构以表现"整体/部分"的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
6.2.1组合模式通常包括以下几个角色:
- 抽象组件(Component) :定义统一的接口或抽象类,规定了叶子节点对象和容器节点对象的共有操作。在抽象组件中通常会声明一些基础的操作,如
operation()
,以及用于管理子组件的接口方法,例如add()
、remove()
等。 - 叶子节点(Leaf):表示组合中的最小构建块,没有子节点。它实现了抽象组件接口的行为,但对于添加或删除子项的操作不进行实际操作,因为叶子节点下不能再包含其他节点。
- 组合对象(Composite):是一个容器节点,可以包含叶子节点或其他组合对象作为其子节点。它同样实现抽象组件的接口,并重写操作方法来对子组件进行管理。
6.2.2.组合模式适用情形:
- 当你希望客户端忽略组合与单个对象的不同,能够统一对待它们时。
- 当希望处理一个对象集合,并且这个集合可以递归地包含其他子集合或成员时。
- 当想表示对象的部分-整体层次结构时。
例如,在管理公司的组织架构时,可以使用组合模式来表示部门和员工的关系。部门可以作为组合对象,包含多个子部门或员工,而员工则是叶子节点。通过这种方式,可以轻松地对整个组织结构进行遍历、添加或移除部门和员工等操作。
综上所述,组合模式的优点在于能够简化客户端代码,因为客户端无需区分是处理单个对象还是组合对象;同时也使得在设计和实现树状结构时更加灵活和简单。不过,需要注意的是,在使用组合模式时,应确保组件的接口足够通用,以便适应各种类型的子组件。
7、享元模式(Flyweight)
7.1.代码实现
java
//操作步骤一:抽象享元类
public abstract class Flyweight {
private String key;
public Flyweight(String key) {
this.key = key;
}
public abstract void operation();
public String getKey() {
return key;
}
}
//操作步骤二:具体享元类A
public class ConcreteFlyweightA extends Flyweight {
public ConcreteFlyweightA(String key) {
super(key);
}
@Override
public void operation() {
System.out.println("ConcreteFlyweightA with key: " + getKey());
}
}
//操作步骤二:具体享元类B
public class ConcreteFlyweightB extends Flyweight {
public ConcreteFlyweightB(String key) {
super(key);
}
@Override
public void operation() {
System.out.println("ConcreteFlyweightB with key: " + getKey());
}
}
//操作步骤三:享元工厂类
public class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
if (key.equals("A")) {
flyweights.put(key, new ConcreteFlyweightA(key));
} else if (key.equals("B")) {
flyweights.put(key, new ConcreteFlyweightB(key));
}
}
return flyweights.get(key);
}
}
//测试代码
public class FlyweightTest {
/**
* 1.Flyweight是抽象享元类,定义了一个operation()方法用于执行具体的操作。
* 2.ConcreteFlyweightA和ConcreteFlyweightB是具体享元类,分别实现了Flyweight接口,
并重写了operation()方法。
* 3.FlyweightFactory是享元工厂类,负责创建和管理享元对象。
*
* 在客户端代码中,通过调用FlyweightFactory的getFlyweight()方法来获取享元对象,并执行相应的操作。
* 由于享元对象的共享特性,当请求相同的享元时,会返回已经存在的享元对象,从而节省内存开销。
* */
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweightA1 = factory.getFlyweight("A");
Flyweight flyweightA2 = factory.getFlyweight("A");
Flyweight flyweightB1 = factory.getFlyweight("B");
flyweightA1.operation(); // 输出:ConcreteFlyweightA with key: A
flyweightA2.operation(); // 输出:ConcreteFlyweightA with key: A
flyweightB1.operation(); // 输出:ConcreteFlyweightB with key: B
}
}
7.2.概念总结
享元模式是一种结构型设计模式,旨在减少对象的创建数量,以节省内存空间和提高程序性能。
以下是关于享元模式的一些关键信息:
-
角色组成:它通常包括以下几个角色:
- 享元工厂(Flyweight Factory):负责创建和管理享元对象,当客户端请求一个享元时,享元工厂会检查是否已经有现成的享元对象可以提供,如果没有,它会创建一个新的享元对象。
- 抽象享元(Abstract Flyweight):定义一个接口,通过这个接口,享元工厂可以创建具体享元类的实例。
- 具体享元(Concrete Flyweight):实现抽象享元接口的具体类,这些类的对象可以被共享使用。
-
与单例模式的区别:享元模式涉及多个对象,而单例模式确保一个类只有一个实例。享元模式的目的是节约内存,而单例模式则是为了控制资源的访问或确保某个类有且仅有一个实例。
-
应用场景:适用于系统中存在大量相似对象,而这些对象又可以通过共享某些相同数据来减少内存占用的情况。例如,文档编辑器中的字体、图像编辑软件中的图标等,都可以通过享元模式来实现高效的内存管理。
-
优势:享元模式通过共享已经存在的对象来大幅度减少需要创建的对象数量,避免了因大量相似对象的创建而导致的内存浪费,从而提高了系统资源的利用率。
综上所述,享元模式是一种优化内存使用的设计模式,适用于需要大量相似对象的场合,通过共享对象来减少内存开销,提升系统性能。