代理模式
代理模式(Proxy Pattern) 是一种结构型设计模式,它通过引入一个代理对象来控制对另一个对象的访问。代理对象通常充当实际对象的"替身",可以在实际对象操作之前、之后进行一些额外的处理。
implements implements has a uses Subject +request() RealSubject +request() Proxy +RealSubject +request() Client
代理模式通常涉及以下几种角色:
- 抽象主题(Subject):定义了真实对象和代理对象的共同接口,使得客户端可以通过代理对象来操作真实对象。
- 真实主题(RealSubject):定义了代理对象所代表的具体对象,包含实际的业务逻辑。
- 代理(Proxy):代理对象和真实对象实现相同的接口,负责控制对真实对象的访问,可能会在访问之前或之后添加一些额外的功能。
代理模式简单实现
当我拜托别人帮我买东西。
(1)抽象主题类
抽象主题类具有真实主题类和代理类的共同接口方法,共同的方法就是购买:
java
public interface IShop {
void buy();
}
(2)真实主题类
实现IShop接口提供的buy方法:
java
public class Me implements IShop{
@Override
public void buy() {
System.out.println("购买");
}
}
(3)代理类
代理类同样也要实现IShop接口,并且要持有被代理者,在buy方法中调用了被代理者的buy方法:
java
public class Purchasing implements IShop{
private IShop mShop;
public Purchasing(IShop shop) {
mShop = shop;
}
@Override
public void buy() {
mShop.buy();
}
}
(4)客户端类
java
public class Client {
public static void main(String[] args) {
IShop me = new Me();
IShop purchasing = new Purchasing(me);
purchasing.buy();
}
}
客户端类的代码就是代理类包含了真实主题类(被代理者),最终调用的都是真实主题类(被代理者)实现的方法。
动态代理简单实现
从编码的角度来看,代理模式分为静态代理和动态代理。上面是静态代理,代码运行前就已经存在了代理类的class编译文件;而动态代理则是在代码运行的时候通过反射来动态地生成代理类的对象,并确定到底代理谁。
在代码运行时决定。Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke方法。下面我们在上面静态代理的例子上做修改。首先创建动态代理类,代码如下所示:
java
public class DynamicPurchasing implements InvocationHandler {
// 被代理的对象
private Object obj;
// 构造函数,接收被代理对象
public DynamicPurchasing(Object obj) {
this.obj = obj;
}
/**
* 动态代理中核心方法,用于处理代理对象的所有方法调用。
* @param proxy 代理对象(动态生成)
* @param method 被调用的方法对象
* @param args 方法调用的参数
* @return 方法执行的结果
* @throws Throwable 抛出的任何异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用被代理对象的实际方法,传入参数并返回结果
Object result = method.invoke(obj, args);
// 如果调用的方法名是 "buy",则添加额外逻辑
if (method.getName().equals("buy")) {
System.out.println("买买买"); // 增强功能:在方法调用后打印
}
// 返回实际方法的执行结果
return result;
}
}
在动态代理类中我们声明了一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke方法中执行。接下来我们修改客户端类的代码:
java
public class Client {
public static void main(String[] args) {
// 创建被代理对象 shop,实际类型为 Me
IShop shop = new Me();
// 创建动态代理处理器,传入被代理对象
DynamicPurchasing mDynamicPurchasing = new DynamicPurchasing(shop);
// 获取被代理对象的类加载器
ClassLoader loader = shop.getClass().getClassLoader();
// 使用 Proxy 类的 newProxyInstance 方法创建代理对象
IShop purshing = (IShop) Proxy.newProxyInstance(
loader, // 类加载器
new Class[]{IShop.class}, // 被代理接口的 Class 对象数组
mDynamicPurchasing // 代理处理器
);
// 调用代理对象的 buy 方法
purshing.buy();
}
}
代理模式类型和优点
类型:
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的实现隐藏。
- 虚拟代理:使用一个代理对象表示一个十分耗费资源的对象并在真正需要时才创建。
- 安全代理:用来控制真实对象访问时的权限。一般用于真实对象有不同的访问权限时。
- 智能指引:当调用真实的对象时,代理处理另外一些事,比如计算真实对象的引用计数当该对象没有引用时,可以自动释放它;或者在访问一个实际对象时,检查是否已经能够锁定它,以确保其他对象不能改变它。
优点:
- 真实主题类就是实现实际的业务逻辑,不用关心其他非本职的工作。
- 真实主题类随时都会发生变化;但是因为它实现了公共的接口,所以代理类可以不做任何修改就能够使用。
装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,允许通过将对象与其他功能"包装"起来,来动态地扩展其功能,而无需改变原有对象的代码。它提供了一种灵活的方式来对一个对象进行功能扩展,通常用于代替继承,以避免类层次结构的僵化。
decoration方法对原Component进行装饰 Component +operation() : void ConcreteComponent +operation() : void Decorator +operation() : void ConcreteDecorator +operation() : void +decoration() : void
- Component:抽象组件,可以是接口或是抽象类,即被装饰的最原始的对象。
- ConcreteComponent:组件具体实现类。即Component的具体实现类,被装饰的具体对象。
- Decorator:抽象装饰者。从外类来拓展Component类的功能,但对于Component来说无须知道 Decorator的存在。在它的属性中必然有一个 private 变量指向Component(抽象组件)。
- ConcreteDecorator:装饰者的具体实现类
装饰模式简单实现
下面是一个关于咖啡的例子,它演示了如何使用装饰模式动态地为咖啡添加不同的配料(如牛奶、糖等):
假设你有一个简单的咖啡店,提供不同种类的咖啡。每种咖啡的价格不同,你还可以为咖啡添加不同的配料。传统的做法是通过继承创建不同类型的咖啡类,但这样做可能会导致类数量急剧增加,特别是当每种配料和咖啡类型的组合多时。使用装饰模式,可以有效地避免这个问题。
首先,我们定义一个 Coffee
接口,所有咖啡和装饰器都要实现这个接口。
(1)定义 Coffee
接口
java
// Component
public interface Coffee {
double cost(); // 计算咖啡的价格
}
(2)创建具体的 Coffee
实现类
例如,创建一个 SimpleCoffee
类,表示普通的黑咖啡。
java
// ConcreteComponent
public class SimpleCoffee implements Coffee {
@Override
public double cost() {
return 5.0; // 简单咖啡的价格是 5.0
}
}
(3)创建装饰器抽象类
接下来,我们定义一个装饰器类 CoffeeDecorator
,它实现了 Coffee
接口,并且持有一个 Coffee
对象作为成员变量,这样我们就能通过它来调用被装饰对象的功能。
java
// Decorator
public class CoffeeDecorator implements Coffee {
protected Coffee coffee; // 持有一个 Coffee 对象
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public double cost() {
return coffee.cost(); // 返回被装饰对象的价格
}
}
(4)创建具体装饰器
然后,我们为每个配料创建一个具体的装饰器类。例如,创建 MilkDecorator
和 SugarDecorator
,分别表示牛奶和糖的装饰。
java
// ConcreteDecorator 1: 添加牛奶
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return super.cost() + 1.5; // 添加牛奶的额外费用
}
}
// ConcreteDecorator 2: 添加糖
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return super.cost() + 0.5; // 添加糖的额外费用
}
}
(5)客户端代码
最后,在客户端代码中,我们可以通过装饰器来动态地组合不同的配料。
java
public class CoffeeShop {
public static void main(String[] args) {
// 创建一个简单的黑咖啡
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Cost of simple coffee: " + simpleCoffee.cost());
// 为咖啡添加牛奶
Coffee milkCoffee = new MilkDecorator(simpleCoffee);
System.out.println("Cost of milk coffee: " + milkCoffee.cost());
// 为牛奶咖啡添加糖
Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee);
System.out.println("Cost of sugar milk coffee: " + sugarMilkCoffee.cost());
}
}
输出结果
当运行 CoffeeShop.main()
方法时,输出将是:
Cost of simple coffee: 5.0
Cost of milk coffee: 6.5
Cost of sugar milk coffee: 7.0
通过装饰模式,我们可以灵活地为不同类型的咖啡添加各种配料,而不需要修改原有的 SimpleCoffee
类。这使得功能的扩展变得非常灵活,而且组合方式可以动态地改变。
装饰模式的优缺点和使用场景
优点:
- 灵活性高:可以在运行时动态地给对象添加行为,而不需要修改对象的代码。
- 避免继承带来的问题:通过装饰器的组合,可以灵活扩展功能,而不需要创建大量的子类。
- 功能组合:可以通过多个装饰器组合不同的功能。
缺点:
- 类的数量增加:虽然每个装饰器类相对简单,但装饰器的数量会随着功能的增加而增加,可能会导致类的数量过多。
- 复杂性增加:当装饰器层次过多时,可能导致系统变得复杂,尤其是调试时可能需要追踪多个装饰器之间的调用关系。
使用场景:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能可以动态地撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
外观模式
外观模式(Facade Pattern)是一种结构型设计模式,旨在为复杂子系统提供一个统一的接口,使得客户端可以更容易地与子系统进行交互。通过外观模式,客户端无需了解系统内部复杂的实现细节,而只需依赖外观类提供的简化接口。
Client Facade +methodA() +methodB() SubsystemA +operationA() SubsystemB +operationB()
外观模式简单实现
实际场景:家庭影院系统
假设你有一个家庭影院系统,系统中包含多个设备:投影仪、音响系统、DVD播放器、灯光等。每个设备都有自己独立的操作方式,可能需要多个步骤才能让系统正常工作。为了避免客户端直接与每个子系统交互并处理每个设备的细节,我们可以使用外观模式来提供一个简化的操作接口。
(1)需求背景
假设你希望在按下"开始看电影"按钮时,自动:
- 打开投影仪
- 启动音响系统
- 播放电影
- 调整灯光
如果没有外观模式,客户端就需要直接与每个设备的多个方法交互。使用外观模式后,客户端只需调用一个方法,即可自动完成这些操作。
(2)子系统类设计
我们可以将家庭影院中的各个设备(投影仪、音响、DVD播放器、灯光等)设计为子系统类。每个子系统负责一项特定的任务。
投影仪类(Projector)
java
public class Projector {
public void on() {
System.out.println("Projector is on.");
}
public void off() {
System.out.println("Projector is off.");
}
public void setInput(String input) {
System.out.println("Setting projector input to " + input);
}
}
音响系统类(SoundSystem)
java
public class SoundSystem {
public void on() {
System.out.println("Sound system is on.");
}
public void off() {
System.out.println("Sound system is off.");
}
public void setVolume(int volume) {
System.out.println("Setting sound system volume to " + volume);
}
}
DVD播放器类(DVDPlayer)
java
public class DVDPlayer {
public void on() {
System.out.println("DVD player is on.");
}
public void off() {
System.out.println("DVD player is off.");
}
public void play(String movie) {
System.out.println("Playing movie: " + movie);
}
}
灯光系统类(Lights)
java
public class Lights {
public void on() {
System.out.println("Lights are on.");
}
public void off() {
System.out.println("Lights are off.");
}
public void dim(int level) {
System.out.println("Dimming lights to " + level + "%");
}
}
(3)外观类设计
为了简化这些复杂的操作,我们定义一个 HomeTheaterFacade
外观类,提供一个简单的接口来控制整个家庭影院系统。
java
public class HomeTheaterFacade {
private Projector projector;
private SoundSystem soundSystem;
private DVDPlayer dvdPlayer;
private Lights lights;
public HomeTheaterFacade(Projector projector, SoundSystem soundSystem, DVDPlayer dvdPlayer, Lights lights) {
this.projector = projector;
this.soundSystem = soundSystem;
this.dvdPlayer = dvdPlayer;
this.lights = lights;
}
// 简化操作:准备电影
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
lights.dim(10); // 调暗灯光
projector.on(); // 打开投影仪
projector.setInput("DVD"); // 设置输入源
soundSystem.on(); // 打开音响
soundSystem.setVolume(5); // 设置音响音量
dvdPlayer.on(); // 打开DVD播放器
dvdPlayer.play(movie); // 播放电影
}
// 简化操作:关闭系统
public void endMovie() {
System.out.println("Shutting down movie theater...");
lights.on(); // 打开灯光
projector.off(); // 关闭投影仪
soundSystem.off(); // 关闭音响
dvdPlayer.off(); // 关闭DVD播放器
}
}
(4)客户端代码
现在,客户端只需通过 HomeTheaterFacade
来控制家庭影院系统,而无需与每个子系统直接交互。
java
public class Client {
public static void main(String[] args) {
// 创建子系统对象
Projector projector = new Projector();
SoundSystem soundSystem = new SoundSystem();
DVDPlayer dvdPlayer = new DVDPlayer();
Lights lights = new Lights();
// 创建外观类
HomeTheaterFacade homeTheater = new HomeTheaterFacade(projector, soundSystem, dvdPlayer, lights);
// 客户端通过外观类简化操作
homeTheater.watchMovie("Inception");
System.out.println("\n--- Movie Finished ---");
homeTheater.endMovie();
}
}
(5) 运行结果
Get ready to watch a movie...
Dimming lights to 10%
Projector is on.
Setting projector input to DVD
Sound system is on.
Setting sound system volume to 5
DVD player is on.
Playing movie: Inception
--- Movie Finished ---
Lights are on.
Projector is off.
Sound system is off.
DVD player is off.
外观模式优缺点和使用场景
优点:
- 外观模式简化了客户端与复杂子系统的交互,减少了客户端的复杂度;
- 通过外观类将系统各个子系统解耦,降低了客户端与系统间的耦合度;
- 提高了系统的可维护性和可扩展性,易于升级和修改;
- 封装了系统复杂性,使得客户端无需了解系统的实现细节。
缺点:
- 可能导致外观类本身过于庞大,增加维护难度;
- 如果不小心,外观类可能掩盖潜在的问题;
- 可能会过度简化,导致不必要的复杂性。
使用场景:
- 适用于多个复杂子系统协作的场景;
- 适用于需要提供统一、简单接口的系统;
- 适用于系统升级和接口替换的场景;
- 适用于需要隐藏子系统细节并简化客户端操作的场景。
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享对象来支持大量细粒度对象的复用,从而减少内存消耗和提高性能。这种模式特别适用于系统中存在大量相似对象时,可以通过共享相同的状态来避免创建大量重复的对象。
要求细粒度对象,那么不可避免地会使得对象数量多且性质相近。这些对象分为两个部分:内部状态和外部状态。内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;而外部状态是对象依赖的一个标记,它是随环境改变而改变的并且不可共享的状态。
creates Flyweight +operation() ConcreteFlyweight +operation() FlyweightFactory +getFlyweight(key)
在享元模式中有如下角色:
- Flyweight:抽象享元角色,同时定义出对象的外部状态和内部状态的接口或者实现
- ConcreteFlyweight:具体享元角色,实现抽象享元角色定义的业务。
- FlyweightFactory:享元工厂,负责管理对象池和创建享元对象。
享元模式简单实现
某著名网上商城卖商品,如果每个用户下单都生成商品对象,这显然会耗费很多资源。如果赶上 "双 11",那 "恐怖" 的订单量会生成很多商品对象,更何况商城卖的商品种类繁多,这样就极易产生"Out Of Memory"现象。因此,我们采用享元模式来对商品的创建进行优化。
(1)抽象享元角色
抽象享元角色是一个商品接口,它定义了showGoodsPrice 方法来展示商品的价格:
java
public interface IGoods {
public void showGoodsPrice(String name);
}
(2)具体享元角色
定义类 Goods,它实现IGoods 接口,并实现了showGoodsPrice 方法,如下所示:
java
public class Goods implements IGoods{
private String name;
private String version;
Goods(String name) {
this.name = name;
}
@Override
public void showGoodsPrice(String name) {
if (version.equals("32G")) {
System.out.println("32G" + name + "价格是:5199");
} else if (version.equals("128G")) {
System.out.println("128G" + name + "价格是:5999");
}
}
}
name为内部状态,version为外部状态。showGoodsPrice方法根据version的不同就会打印出不同的价格。
(3)享元工厂
java
public class GoodsFactory {
private static Map<String, Goods> pool = new HashMap<String, Goods>();
public static Goods getGoods(String name) {
if (pool.containsKey(name)) {
System.out.println("从缓存中获取,key为:" + name);
return pool.get(name);
} else {
Goods goods = new Goods(name);
pool.put(name, goods);
System.out.println("创建商品,key为:" + name);
return goods;
}
}
}
享元工厂 GoodsFactory 用来创建 Goods 对象。通过 Map 容器来存储 Goods 对象,将内部状态 name 作为 Map 的 key ,以便标识 Goods 对象。如果 Map 容器中包含此 key ,则使用 Map容器中存储的 Goods 对象;否则就新创建 Goods 对象,并放入 Map 容器中。
(4)客户端调用
在客户端中调用 GoodsFactory的 getGoods方法来创建 Goods 对象,并调用 Goods 的showGoodsPrice 方法来显示产品的价格,如下所示:
java
public class Client {
public static void main(String[] args) {
Goods goods1 = GoodsFactory.getGoods("iphone7");
goods1.showGoodsPrice("32G");
Goods goods2 = GoodsFactory.getGoods("iphone7");
goods2.showGoodsPrice("32G");
Goods goods3 = GoodsFactory.getGoods("iphone7");
goods3.showGoodsPrice("128G");
}
}
(5)运行结果
创建商品,key为:iphone7价格为5199元
从缓存中获取,key为:iphone7价格为5199元
从缓存中获取,key为:iphone7价格为5999元
从输出中可以看出,只有第一次创建了Goods对象;后面因为key值相同,所以均使用了对象池中的 Goods 对象。在这个例子中,name 作为内部状态是不变的,并且作为Map的key值是可以共享的。而 showGoodsPrice 方法中需要传入的 version 值则是外部状态,它的值是变化的。
享元模式使用场景
享元模式特别适用于以下场景:
- 存在大量细粒度的、相似的对象。
- 对象的部分状态可以共享,且这些状态是不可变的。
- 频繁创建和销毁大量对象,导致内存消耗和性能问题。
- 对象的状态可以分为内在状态和外部状态,内在状态可以被共享。
通过共享内在状态,享元模式能够显著减少对象的数量,从而节省内存和提高性能,尤其在高并发和大规模系统中,享元模式是一种非常有效的优化策略。
已经到底啦!!