装饰者模式(Decorator)
Component:组件(主体)
concreteComponent:被装饰者
Decorator:装饰者
注意:concreteComponent、Decorator都会实现或继承Component
角色
Client 类:装饰模式的调用者
Component:主体===》饮料(Drink)
concreteComponent:被装饰者===》咖啡(Coffee)
xxxConcreteComponent:具体的被装饰者(ChinaCoffee)美式咖啡、欧式咖啡、中式咖啡
Decorator:装饰器===》调料
xxxDecorator:子装饰器糖类、奶酪、牛油、芥末
案例
单体咖啡与调味组合的饮料计价项目
- 使用前
这个项目最容易想到的设计方案就是采用继承的设计方案,设计思路如下:
Drink===》饮品
Juice===》果汁
.......
Coffee===》咖啡
ChinaCoffee===》中式咖啡
ASeasoningChinaCoffee===》被A调料修饰的中式咖啡
BSeasoningChinaCoffee===》被B调料修饰的中式咖啡
......
XxxCoffee===》其它咖啡
ASeasoningXxxCoffee===》被A调料修饰的其它咖啡
BSeasoningXxxCoffee===》被B调料修饰的其它咖啡
.....
上面每个类中都有getPrice计价的功能,从结果上来看,确实可以完成项目需求,但是整个设计体系过于臃肿,不便于后期的扩展与维护;
①.Drink类
/**
* 饮料包括单体咖啡+调料
*/
public abstract class Drink {
protected double price;
protected int n;
protected DrinkSeasoning seasoning;
public abstract double getPrice();
}
/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}
/**
* 单体果汁
*/
abstract class Juice extends Drink {
}
class ChinaCoffee extends Coffee{
ChinaCoffee(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n+this.seasoning.getPrice();
}
}
②.DrinkSeasoning类
/**
* 调料的抽象接口
*/
public interface DrinkSeasoning {
public abstract double getPrice();
}
/**
* A类调料
*/
class ADrinkSeasoning implements DrinkSeasoning{
protected double price;
protected int n;
ADrinkSeasoning(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n;
}
}
/**
* B类调料
*/
class BDrinkSeasoning implements DrinkSeasoning{
private double price;
protected int n;
BDrinkSeasoning(double price,int n){
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price*this.n;
}
}
③.Client类
public class Client {
public static void main(String[] args) {
ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
ADrinkSeasoning aDrinkSeasoning = new ADrinkSeasoning(2,2);
chinaCoffee.seasoning = aDrinkSeasoning;
System.out.println("中式咖啡1份+A调料2份,最终价格为:"+chinaCoffee.getPrice());
// 思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
seasoning只能代表一种调料
// 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
原有的设计没办法应对这种场景
}
}
执行结果为:
- 使用后
①.Drink类
public abstract class Drink {
protected double price;
protected int n;
public abstract double getPrice();
}
/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}
/**
* 单体果汁
*/
abstract class Juice extends Drink {
}
class ChinaCoffee extends Coffee {
ChinaCoffee(double price, int n) {
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price * this.n;
}
}
②.DecoratorDrink类
public class DecoratorDrink extends Drink {
private Drink drink;
public DecoratorDrink(Drink drink, double price, int n) {
this.drink = drink;
this.price = price;
this.n = n;
}
@Override
public double getPrice() {
return this.price * this.n + drink.getPrice();
}
}
class ADecoratorDrink extends DecoratorDrink {
public ADecoratorDrink(Drink drink, double price, int n) {
super(drink, price, n);
}
}
class BDecoratorDrink extends DecoratorDrink {
public BDecoratorDrink(Drink drink, double price, int n) {
super(drink, price, n);
}
}
③.Client类
public class Client {
public static void main(String[] args) {
ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
// 假定A类调料2元一份,B类调料3元一份
Drink order = new ADecoratorDrink(chinaCoffee, 2, 2);
System.out.println("中式咖啡1份+A调料2份,最终价格为:"+order.getPrice());
// 思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
order = new ADecoratorDrink(order,2,1);
System.out.println("式咖啡1份+A调料3份,最终价格为:"+order.getPrice());
order = new BDecoratorDrink(order,3,2);
System.out.println("式咖啡1份+A调料3份+B调料2份,最终价格为:"+order.getPrice());
// 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
order = new BDecoratorDrink(order,3,2);
System.out.println("式咖啡1份+A调料3份+B调料4份,最终价格为:"+order.getPrice());
}
}
执行结果为:
注意事项及细节
装饰者模式一般用于对原有功能进行增强/装饰
动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性
应用
- IO流体系:缓冲流
外观模式(Facade)
- 术语
Facade:外观
角色
Client 类
外观模式的调用者
ComponentA
子系统A
电脑
ComponentB
子系统B
投影仪
Facade
HomeTheaterFacade
家庭影院
案例
需求:组装一个家庭影院;
电脑打开、投影仪打开、音箱打开、灯光调暗、零食拿出来,电影开始;
零食收起来、灯光调亮、音箱关闭、投影仪关闭、电脑关闭,电影结束;
- 使用前
/**
* 电脑(故意写两个用不上的功能,依次体现外观模式的优点)
*/
public class ComponentA {
public void m1(){
System.out.println("电脑功能一...");
}
public void m2(){
System.out.println("电脑功能二...");
}
public void on(){
System.out.println("电脑打开...");
}
public void off(){
System.out.println("电脑关闭...");
}
}
//投影仪
class ComponentB {
public void on(){
System.out.println("投影仪打开...");
}
public void off(){
System.out.println("投影仪关闭...");
}
}
//音箱
class ComponentC {
public void on(){
System.out.println("音箱打开...");
}
public void off(){
System.out.println("音箱关闭...");
}
}
//、灯光
class ComponentD {
public void on(){
System.out.println("灯光调亮...");
}
public void off(){
System.out.println("灯光调暗...");
}
}
//零食
class ComponenE {
public void on(){
System.out.println("零食拿出来...");
}
public void off(){
System.out.println("零食收起来...");
}
}
public class Client {
public static void main(String[] args) {
new ComponentA().on();
new ComponentB().on();
new ComponentC().on();
new ComponentD().off();
new ComponenE().on();
System.out.println("电影开始了...");
System.out.println();
new ComponenE().off();
new ComponentD().on();
new ComponentC().off();
new ComponentB().off();
new ComponentA().off();
System.out.println("电影结束了...");
}
}
从上面代码可以看出:
客户端调用依赖了所有的子系统(ABCDE),如果该需求反复出现,对于客户端调用而言,就不是很方便了;
另一方面,此需求完成只需要依赖各个子系统的其中一部分功能,其它功能客户端用不上,依照迪米特法则我们也应该解耦客户端与各个子系统的关系;
- 使用后
public class ComponentA {
public void m1(){
System.out.println("电脑功能一...");
}
public void m2(){
System.out.println("电脑功能二...");
}
public void on(){
System.out.println("电脑打开...");
}
public void off(){
System.out.println("电脑关闭...");
}
}
//投影仪
class ComponentB {
public void on(){
System.out.println("投影仪打开...");
}
public void off(){
System.out.println("投影仪关闭...");
}
}
//音箱
class ComponentC {
public void on(){
System.out.println("音箱打开...");
}
public void off(){
System.out.println("音箱关闭...");
}
}
//、灯光
class ComponentD {
public void on(){
System.out.println("灯光调亮...");
}
public void off(){
System.out.println("灯光调暗...");
}
}
//零食
class ComponentE {
public void on(){
System.out.println("零食拿出来...");
}
public void off(){
System.out.println("零食收起来...");
}
}
public class ComponentFacade {
ComponentA componentA =new ComponentA();
ComponentB componentB = new ComponentB();
ComponentC componentC = new ComponentC();
ComponentD componentD = new ComponentD();
ComponentE componenE = new ComponentE();
public void on(){
componentA.on();
componentB.on();
componentC.on();
componentD.off();
componenE.on();
System.out.println("电影开始了...");
}
public void off(){
componenE.off();
componentD.on();
componentC.off();
componentB.off();
componentA.off();
System.out.println("电影结束了...");
}
}
public class Client {
public static void main(String[] args) {
ComponentFacade componentFacade = new ComponentFacade();
componentFacade.on();
System.out.println();
componentFacade.off();
}
}
从上面可以看出,客户端只依赖了外观类,彻底解耦了与各个子系统之间的关系;
注意事项及细节
屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展
当系统需要进行分层设计时,可以考虑使用 Facade 模式
应用
后期代码维护,提取某些类中可用的方法聚合在一起
代理模式(Proxy)
- 分类
-- 静态代理
角色
接口
ITeacherDao
目标对象
TeacherDAO
代理类
TeacherDAOProxy
细节
代理对象与目标对象要实现相同的接口
调用的时候通过调用代理对象的方法来调用目标对象
①.TeacherDAO类
public class TeacherDAO implements ITeacherDao {
public void teach() {
System.out.println("老师传授知识");
}
}
//目标接口
interface ITeacherDao {
void teach();
}
//代理类
class TeacherDAOProxy implements ITeacherDao {
private ITeacherDao teacherDAO;
public TeacherDAOProxy(ITeacherDao teacherDAO) {
this.teacherDAO = teacherDAO;
}
@Override
public void teach() {
System.out.println("老师正式授课前的准备工作,如学生全部签到...");
teacherDAO.teach();
System.out.println("老师结束授课,如下课铃声响起...");
}
}
②.Client类
public class Client {
public static void main(String[] args) {
TeacherDAOProxy proxy = new TeacherDAOProxy(new TeacherDAO());
proxy.teach();
}
}
- 动态代理
- jdk代理
角色
接口
ITeacherDao
目标对象
TeacherDAO
代理类
TeacherDAOProxy
细节
不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
代理类所在包:java.lang.reflect.Proxy
①.ITeacherDao类
/**
* 目标接口
*/
interface ITeacherDao {
String teach();
ITeacherDao sleep(int minutes);
}
class TeacherDao implements ITeacherDao{
@Override
public String teach() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(new Date())+":老师传授知识";
}
@Override
public ITeacherDao sleep(int minutes) {
System.out.println("老师睡了" + minutes + "分钟");
return this;
}
}
//真实代理类的外衣
class TeacherDaoProxy{
private ITeacherDao target;
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
public Object xxx(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
String methodName = method.getName();
System.out.println("目标方法" + methodName + ":jdk代理开始...");
System.out.println("真实代理对象:"+proxy.getClass());
System.out.println("目标对象:"+target.getClass());
if("sleep".equals(methodName)){
// method.invoke(target, args);
// obj = proxy;
obj = method.invoke(target, args);
}else {
// proxy是真实代理类,method是目标方法,args是目标方法携带的参数
obj = method.invoke(target, args);
}
System.out.println("目标方法" + methodName + ":jdk代理结束...");
return obj;
}
});
}
}
②.Client类
public class Client {
public static void main(String[] args) {
TeacherDaoProxy proxy = new TeacherDaoProxy(new TeacherDao());
ITeacherDao ins = (ITeacherDao) proxy.xxx();
System.out.println("===========代理类实例被使用 begin=============");
System.out.println(ins);
System.out.println("===========代理类实例被使用 end=============");
System.out.println(ins.teach());
// System.out.println(proxy.execute());
System.out.println("===========代理类实例被使用 begin=============");
ins.sleep(10);
System.out.println("===========代理类实例被使用 end=============");
ins.sleep(20).sleep(60);
}
}
执行结果为:
Cglib代理
角色
接口
ITeacherDao
目标对象
TeacherDAO
代理类
TeacherDAOProxy
细节
目标对象与代理对象都不需要实现接口
Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展
Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用
Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
注意:
1)需要引入 cglib 的 jar 文件
2)在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException:
3)目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
class TeacherDao {
public String teach() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(new Date()) + ":老师传授知识";
}
public TeacherDao sleep(int minutes) {
System.out.println("老师睡了" + minutes + "分钟");
return this;
}
}
//真实代理类的外衣
class TeacherDaoProxy implements MethodInterceptor {
private Object target;
public TeacherDaoProxy(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
/**
* @param proxyIns 由CGLib动态生成的代理类实例
* @param method 上文中实体类所调用的被代理的方法引用
* @param args 参数值列表
* @param methodProxy 生成的代理类对方法的代理引用
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxyIns, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
Object res;
System.out.println("目标方法" + methodName + ":cglib代理开始...");
System.out.println("真实代理对象:" + proxyIns.getClass());
System.out.println("目标对象:" + target.getClass());
if ("sleep".equals(methodName)) {
// method.invoke(target, args);
// obj = proxy;
res = method.invoke(target, args);
// res = methodProxy.invokeSuper(proxyIns,args);
res = proxyIns;
} else {
// proxy是真实代理类,method是目标方法,args是目标方法携带的参数
res = method.invoke(target, args);
}
System.out.println("目标方法" + methodName + ":cglib代理结束...");
return res;
}
}
public class Client {
public static void main(String[] args) {
TeacherDao proxy = (TeacherDao) new TeacherDaoProxy(new TeacherDao()).getProxyInstance();
proxy.sleep(111).sleep(222);
}
}
最后执行结果为:
至此,装饰者模式&外观模式&代理模式介绍完毕,由于作者水平有限难免有疏漏,欢迎留言纠错。