设计模式-结构性模式

结构型模式

  • 1. 适配器模式(Adapter Pattern)
  • 2. 桥接模式(Bridge Pattern)
  • 3. 装饰器模式(Decorator Pattern)
    • 步骤 1:定义咖啡接口
    • 步骤 2:具体的咖啡类
    • 步骤 3:装饰器抽象类
    • 步骤 4:具体的装饰器类
    • 步骤 5:使用装饰器
  • 4. 组合模式(Composite Pattern)
    • 4.1 定义组件接口
    • 4.2 实现基本的图形
    • 4.3 实现复合图形
    • 4.4 客户端代码
  • 5. 外观模式(Facade Pattern)
  • 6. 享元模式(Flyweight Pattern)
  • 7.代理模式(Proxy Pattern)
    • 步骤 1:定义EDMO接口
    • 步骤 2:实现EDMO接口
    • 步骤 3:创建动态代理的InvocationHandler
    • 步骤 4:创建EDMO的代理对象并调用

1. 适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern)是一种结构型设计模式,用于将一个类的接口转换成客户端所期待的另一个接口,使因接口不兼容而不能一起工作的类可以一起工作。适配器模式分为类适配器模式和对象适配器模式,这里将提供一个基于对象适配器模式的Java示例。

假设我们有一个老旧的电源插座(OldPowerSocket),它的输出电压和电流与我们新买的电器(NewElectricAppliance)不匹配。为了能让这个新电器使用,我们需要一个适配器(Adapter)来转换插座的输出。

首先,我们定义老旧的电源插座接口和它的实现:

// 老旧电源插座接口
public interface OldPowerSocket {
	int outputVoltage(); // 输出电压
	int outputCurrent(); // 输出电流
}

// 老旧电源插座实现
public class OldPowerSocketImpl implements OldPowerSocket {
	@Override
	public int outputVoltage() {
		return 220; // 假设老旧插座输出电压为220V
	}

	@Override
	public int outputCurrent() {
		return 10; // 假设老旧插座输出电流为10A
	}
}

接下来,我们定义新电器所需要的电源接口

// 新电器电源接口
public interface NewPowerSource {
	int requiredVoltage(); // 所需电压
	int requiredCurrent();// 所需电流
}

现在,我们需要编写一个适配器,它将 OldPowerSocket 适配为 NewPowerSource:

// 适配器
public class PowerAdapter implements NewPowerSource {
	private OldPowerSocket oldSocket;

	public PowerAdapter() {
		this.oldSoket = oldSocket;
	}

	@Override
	public int requiredVoltage() {
		// 这里只是简单的将电压转换为电器所需的电压(假设直接转换即可)
		// 在实际应用中,可能需要更复杂的转换逻辑
		return oldSocket.outputVoltage();
	}

	@Override  
    public int requiredCurrent() {  
        // 假设新电器需要的电流与老旧插座输出电流相同(这里仅为示例)  
        // 在实际应用中,可能需要根据实际情况进行转换  
        return oldSocket.outputCurrent();  
    }
}

最后,我们编写客户端代码来使用这个适配器:

public class Client {  
    public static void main(String[] args) {  
        // 创建老旧电源插座的实例  
        OldPowerSocket oldSocket = new OldPowerSocketImpl();  
  
        // 创建适配器实例,将老旧电源插座适配为新电器所需的电源  
        NewPowerSource newPowerSource = new PowerAdapter(oldSocket);  
  
        // 使用适配后的电源为新电器供电(这里只是演示接口调用)  
        System.out.println("Required Voltage: " + newPowerSource.requiredVoltage());  
        System.out.println("Required Current: " + newPowerSource.requiredCurrent());  
    }  
}

这个示例展示了如何通过适配器模式将一个类(老旧电源插座)的接口适配为另一个类(新电器)所期望的接口,从而允许它们一起工作。在实际应用中,适配器的转换逻辑可能会更加复杂,包括数据格式的转换、性能优化等。

2. 桥接模式(Bridge Pattern)

桥接模式(Bridge Pattern)是一种结构型设计模式,它旨在将抽象部分与它的实现部分分离,使它们都可以独立地变化。在桥接模式中,我们通常会有层次的结构:一个层次是抽象化角色(Abstraction),它维护对实现化角色(Implementor)的引用;另一个层次是实现化角色,它定义了实现接口的类。

以下是一个简单的Java示例,展示了如何使用桥接模式来实现绘图功能,其中图形的颜色和形状分别作为两个独立地层次进行设计和实现。

首先,我们定义实现化角色的接口 DrawAPI:

public interface DrawAPI {  
    void drawCircle(int radius, int x, int y);  // 圆
    void drawRectangle(int width, int height, int x, int y); // 正方形 
}

然后,我们实现这个接口,为不同的颜色提供具体的实现类,比如 RedCircle 和 GreenCircle(注意这里仅为示例,实际中我们可能通过颜色类来同意处理):

// 红色画笔  
public class RedCircle implements DrawAPI {  
    @Override  
    public void drawCircle(int radius, int x, int y) {  
        System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", " + y + "]");  
    }  
  
    @Override  
    public void drawRectangle(int width, int height, int x, int y) {  
        // 这里我们可能不需要实现所有方法,但为了演示接口完整性,我们还是声明了  
        System.out.println("Red does not support drawing rectangle!");  
    }  
}  
  
// 绿色画笔(省略drawRectangle实现,因为它与RedCircle相同)  
public class GreenCircle implements DrawAPI {  
    @Override  
    public void drawCircle(int radius, int x, int y) {  
        System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", " + y + "]");  
    }  
  
    @Override  
    public void drawRectangle(int width, int height, int x, int y) {  
        System.out.println("Green does not support drawing rectangle!");  
    }  
}

接下来,我们定义抽象化角色 Shape,它包含一个 DrawAPI 类型的引用:

public abstract class Shape {  
    protected DrawAPI drawAPI;  
  
    protected Shape(DrawAPI drawAPI) {  
        this.drawAPI = drawAPI;  
    }  
  
    public abstract void draw();  
}

然后,我们创建具体的形状类,如 Circle,并在构造函数中传入 DrawAPI 的实现:

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);  
    }  
}

最后,我们编写客户端代码来演示如何使用桥接模式:

public class BridgePatternDemo {  
    public static void main(String[] args) {  
        Shape redCircle = new Circle(100, 100, 10, new RedCircle());  
        Shape greenCircle = new Circle(100, 100, 10, new GreenCircle());  
  
        redCircle.draw();  
        greenCircle.draw();  
    }  
}

这个示例展示了如何将图形的颜色(实现化角色)和形状(抽象化角色)分离,使得它们可以独立地变化。通过桥接模式,我们可以在不修改现有代码的情况下,增加新的形状或颜色。

3. 装饰器模式(Decorator Pattern)

装饰器模式(Decorator Pattern)是一种结构型设计模式允许向一个现有的对象添加新的功能,同时又不改变其结构。就增加功能来说,装饰器模式相比生成子类更为灵活。这种模式创建了一个包装对象,也就是装饰器,来包裹真实的对象。

在Java中,装饰器模式通常通过创建一个实现了同一个接口的类的包装类(即装饰类)来实现。让我们以一个简单的咖啡订购系统为例,来展示如何使用装饰器模式。

首先,定义一个咖啡的接口,然后定义一个具体的咖啡类(Espresso),之后创建多个装饰器类(如 Milk 和 Whip)来添加不同的配料。

步骤 1:定义咖啡接口

public interface Coffee {
	String getDescription();
	double cost();
}

步骤 2:具体的咖啡类

public class Espresso implements Coffee {
	@Override
	public String getDesription() {
		return "Espresso";
	}

	@Override
	public double cost() {
		return 1.99;
	}
}

步骤 3:装饰器抽象类

public abstract class CoffeeDecorator implements Coffee {
	protected Coffee decoratedCoffee;

	public CoffeeDecorator(Coffee decoratedCoffee) {
		this.decoratedCoffee = decoratedCoffee;
	}

	@Override
	public String getDescription() {
		return decoratedCoffee.getDescrition();
	}

	@Override
	public double cost() {
		return decoratedCoffee.cost();
	}
}

步骤 4:具体的装饰器类

// 牛奶装饰器
public class Milk extends CoffeeDecorator {
	public Milk(Coffee decoratedCoffee) {
		super(decoratedCoffee);
	}

	@Override
	public String getDescription() {
		return decoratedCoffee.getDescrition() + ",Milk";
	}

	@Override
	public double cost() {
		return decoratedCoffee.cost() + 0.10;
	}
}

// 奶泡装饰器
public class Whip extends CoffeeDecorator {
	public Whip() {
		super(decoratedCoffee);
	}

	@Override
	public String getDescription() {
		return decoratedCoffee.getDescription() + ",Whip";
	}

	@Override
	public double cost() {
		return decoratedCoffee.cost() + 0.15;
	}
}

步骤 5:使用装饰器

public class CoffeeOrder {
	public static void main(String[] args) {
		Coffee espresso = new Espresso();
		Coffee espressoWithMilk = new Milk(espresso);
		Coffee espressWithMilkAndWhip = new Whip(espressoWitnMilk);

		Ststem.out.println(esressoWithMilkAndWhip.getDesription() + " $" + espressoWithMilkAndWhip.cost())
		// 输出:Espresso,Milk,Whip $2.24
	}
}

在这个例子中,我们定义了一个 Espresso 咖啡,然后通过 Milk 和 whip 装饰器来增加新的特性和成本。通过组合这些装饰器,我们可以创建出各种不同的咖啡,而无需修改原始的 Espresso 类或定义大量的子类。这就是装饰器模式的强大之处。

4. 组合模式(Composite Pattern)

组合模式(Composite Pattern)允许你将对象组合成树形结构以表示“不分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

以下是一个使用Java实现的组合模式的简单Demo。在这个Demo中,我们将构架一个图形界面组件的层次结构,其中可以包含基本的图形(如矩形、圆形)和符合图形(如图形组)。

4.1 定义组件接口

首先,定义一个所有图形组件都实现的接口,包括基本的图形和符合图形。

public interface Shape {
	void draw(String prefix);
}

4.2 实现基本的图形

然后,实现一些基本的图形,如矩形和圆形。

// 矩形
public class Rectangle implements Shape {
	private String name;

	public Rectangle() {
		this.name = name;
	}

	@Override
	public void draw(){
		System.out.println(prefix + "-Drawing Rectangle: " + name);
	}
}

// 圆形
public class Circle implements Shape {
	private String name;

	public Circle(String name) {
		this.name = name;
	}

	@Override
	public void draw(String prefix) {
		System.out.println(prefix + "-Drawing Circle: " + name);
	}
}

4.3 实现复合图形

接着,实现一个复合图形类,它可以包含多个子图形。

import java.util.ArrayList;
import java.util.List;

public class ShapeGroup implements Shape {
	private List<Shape> shapes;
	private String name;

	public ShapeGroup(String name) {
		this.name = name;
		this.shapes = new ArrayList<>();
	}

	public void add(Shape shape) {
		shapes.add(shape);
	}

	public void remove(Shape shape) {
		shapes.remove(shape);
	}

	@Override
	public void draw(String prefix) {
		System.out.println(prefix + "-Drawing ShapeGroup: " + name);
		for (Shape shape : shapes) {
			shape.draw(prefix + "  ");
		}
	}
}

4.4 客户端代码

最后,编写客户端代码来组合和使用这些图形。

public class CompositePatternDemo {
	public static void main(String[] args) {
		// 创建基本图形
		Shape circle = new Circle("Circle");
		Shape rectangle = new Rectangle("Rectangle");

		// 创建复合图形
		ShapeGroup group = new ShapeGroup("MyShapes");
		group.add(circle);
		group.add(rectangle);
		
		// 添加另一个复合图形
		ShapeGroup anotherGroup = new ShapeGroup("AnotherGroup");
		anotherGroup.add(new Circle("Inner Circle"));
		group.add(anotherGroup);

		// 绘制图形
		group.draw("");
	}
}

输出将类似于:

-Drawing ShapeGroup: MyShapes  
  -Drawing Circle: Circle  
  -Drawing Rectangle: Rectangle  
  -Drawing ShapeGroup: AnotherGroup  
    -Drawing Circle: Inner Circle

在这个Demo中,我们创建了一个简单的图形层次结构,其中ShapeGroup是一个复合图形,可以包含其他Shape对象,无论是基本的图形(如Circle和Rectangle)还是其他复合图形。通过这种方式,我们可以构建复杂的图形结构,并使用统一的接口来操作它们。

5. 外观模式(Facade Pattern)

在Java中,外观模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供一个统一的接口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

以下是一个简单的Java外观模式示例,我们将模拟一个系统,该系统包括几个不同的组件(如日志记录器、数据库访问器等),我们将通过一个外观类来简化对这些组件的访问。

首先,我们定义几个简单的组件接口及其实现:

// 日志记录器接口
interface Logger {
	void log(String message);
}

// 日志记录器实现
class SimpleLogger implements Logger {
	@Override
	public void log(String message) {
		System.out.println("Logging: " + message);
	}
}

// 数据库接口
interface Database {
	void connect();
	void disconnect();
}	

// 数据库实现
class SimpleDatabase implements Database {
	@Override
	public void connect() {
		System.out.println("Connecting to database...");
	}

	@Override
	public void disconnect() {
		System.out.println("Disconnecting from database...");
	}
}

接下来,我们定义一个外观类,该类将上述组件的接口组合成一个更简单的接口:

// 外观类
class SystemFacade {
	private Logger logger;
	private Database database;

	public SystemFacade() {
		this.logger = new SimpleLogger();
		this.database = new SimpleDatabase();
	}

	// 执行一个业务操作,使用到了日志和数据库
	public void performTask() {
		// 使用日志
		logger.log("Starting task...);

		// 使用数据库
		database.connect();
		// 假设这里执行了一些数据库操作
		System.out.println("Performing database operations...");
		database.disconnect();

		// 结束日志
		logger.log("Task completed...");
	}
}

最后,我们通过一个简单的测试类来演示如何使用这个外观类:

public class FacadeDemo {
	public static void main(String[] args) {
		// 创建一个外观类的实例
		SystemFacade facade = new SystemFacade();

		// 调用外观类的方法执行业务操作
		facade.performTask();
	}
}

在这个示例中,SysytemFacade 类提供了一个简单的方法来执行一个涉及日志和数据库操作的业务任务。客户端代码(在这个例子中是 FacadeDemo 类)只需要与 SystemFacade 类交互,而不需要直接与 Logger 和
Database 接口的实现类交互,从而简化了客户端代码并降低了其与子系统内部细节的耦合度。

6. 享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。它通过共享已有的相似对象来避免创建大量重复的实例。在Java中实现享元模式通常涉及以下三个主要角色:

1.Flyweight(抽象享元类):定义了一个接口,通过这个接口可以访问并可能修改一个内部状态(不共享的部分),但通常由Context来维护。

2.ConcreteFlyweight(具体享元类):实现了Flyweight接口,并为内部状态(不共享)和可能有的共享状态提供存储。

3.FlyweightFactory(享元工厂类):负责创建和管理享元对象。它确保合理地共享享元,当用户请求一个享元时,Factory会检查是否已经存在一个符合要求的享元对象,如果存在,就返回这个已有的享元对象,如果不存在,就创建一个新的享元对象。

下面是一个简单的Java实现示例,其中享元对象代表不同的字符(字符的绘制可以通过简单的字符串表示,为了简化,我们这里直接用字符作为享元对象):

import java.util.HashMap;
import java.util.Map;

// 抽象享元类
interface CharacterFlyweight {
	void display(String context);
}

// 具体享元类
class UnicodeCharacter implements CharacterFlyweight {
	private char character;
	
	public UnicodeCharacter(char character) {
		this.character = character;
	}

	@Override
	public void display(String context) {
		System.out.println("Displaying '" + character + "' + context);
	}
}

// 共享工厂类
class CharaterFactory {
	private Map<Character, CharacterFlyweight> flyweights = new HashMap();

	public CharacterFlyweight getFlyweight(char key) {
		CharacterFlyweight fw = flyweight.get(key);
		if(fw == null) {
			fw = new UnicodeCharater(key);
			flyweights.put(key, fw);
		}
		return fw;
	}
}

// 享元模式的使用
public class FlyweightPatternDemo {
	private static final String CONTEXT_A = "Font context A";
	private static final String CONTEXT_B = "Font context B";

	public static void main(String[] args) {
		CharacterFactory factory = new CharacterFactory();

		CharacterFlyweight fw1 = factory.getFlyweight('A');
		fw1.display(CONTEXT_A);

		CharacterFlyweight fw2 = factory.getFlyweight('B');
		fw2.display(CONTEXT_B);

		// 尝试获取相同的字符对象
		CharacterFlyweight fw3 = factory.getFlyweight('A');
		fw3.dispaly(CONTEXT_A + "again");

		// 检查是否指向同一个对象
		System.out.println(fw1 == fw3);// 应该输出 true
	}
}

在这个例子中,CharacterFlyweight 是一个接口,定义了所有享元对象共有的方法。UnicodeCharacter 是实现了 CharacterFlyweight 接口的具体享元类,代表了一个具体的字符对象。CharacterFactory 是享元工厂类,它负责创建和管理享元对象。通过检查已存在的对象来避免重复创建相同的字符对象。最后,在 FlyweightPatternDemo 类中展示了如何使用享元模式。

7.代理模式(Proxy Pattern)

在Java中实现代理模式(Proxy Pattern)主要有两种形式:静态代理和动态代理。静态代理是通过创建一个代理类来显式地指定要代理的方法,而动态代理则是在运行时动态地创建代理类。这里,我们将展示如何使用Java的动态代理来实现一个见得EDMO(这里假设EDMO是一个需要被代理的类,用于演示目的)的代理模式。

首先,我们需要一个接口(Interface)来定义EDMO的行为,然后实现这个接口的类就是EDMO的真实对象。接着我们使用Java的 Proxy 类和 InvocationHandler 接口来创建EDMO的动态代理。

步骤 1:定义EDMO接口

public interface EDMO {
	void performAction();
}

步骤 2:实现EDMO接口

public class RealEDMO implements EDMO {
	@Override
	public void perormAction() {
		System.out.println("EDMO is performing an action...");
	}
}

步骤 3:创建动态代理的InvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class EDMOInvocationHandler implements InvocationHandler {
	private Object target;

	public EDMOInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 在调用目标方法之前可以添加一些逻辑
		System.out.println("Before method: " method.getName());
	
		// 调用目标方法
		Object result = method.invoke(target, args);

		// 在调用目标方法之后可以添加一些逻辑
		System.out.println("After method: " + method.getName());

		return result;
	}
}

步骤 4:创建EDMO的代理对象并调用

import java.lang.reflect.Proxy

public class ProxyDemo {
	public static void main(String[] args) {
		// 创建EDMO的真实对象
		EDMO realEDMO = new RealEDMO();
		
		// 使用Proxy类和InvocationHandler来创建代理对象
		EDMO proxyEDMO = (EDMO) Proxy.newProxyInstance(
			realEDMO.getClass().getClassLoader(),
			new Class[]{EDMO.class},
			new EDMOInvocationHandler(realEDMO)
		)

		// 通过代理对象调用方法
		proxyEDMO.performAction();
	};

}

在这个例子中,EDMOInvocationHandler 是代理逻辑的处理器,它实现了 InvocationHandler 接口。当通过代理对象调用 performAction 方法时,实际上是通过 invoke 方法调用真是对象的 performAction 方法,同时在调用前后添加了额外的逻辑。

这就是使用Java动态代理模式来实现EDMO代理的一个基本示例。动态代理在需要为多个类创建代理时特别有用,因为它避免了为每个类手动编写静态代理的重复工作。

你可能感兴趣的:(设计模式)