设计模式之门面模式与装饰器模式详解和应用

news2024/12/26 0:09:29

目录

  • 1 门面模式定义
    • 1.1 门面模式的应用场景
    • 1.2 门面模式的通用写法
    • 1.3 门面模式业务场景实例
    • 1.4 门面模式在源码中的应用
    • 1.5 门面模式的优缺点
  • 2 装饰器模式
    • 2.1 装饰器模式定义
    • 2.2 装饰器模式的应用场景
    • 2.3 装饰器模式在源码中的应用
    • 2.4 装饰器模式和代理模式对比
    • 2.5 装饰器模式的优缺点


1 门面模式定义

门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接 口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构性模式。

原文: Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use. 解释:要求一个子系统的外部与其内部的通信必须通过一个同一的对象进行。门面模式提供一个高层次的接口,使得 子系统更易于使用。

其实,在我们日常的编码工作中,我们都在有意无意地大量使用门面模式,但凡只要高层模块需要 调度多个子系统(2个以上类对象),我们都会自觉地创建一个新类封装这些子系统,提供精简接口, 让高层模块可以更加容易间接调用这些子系统的功能。尤其是现阶段各种第三方SDK,各种开源类库, 很大概率都会使用门面模式。尤其是你觉得调用越方便的,门面模式使用的一般更多。

1.1 门面模式的应用场景

1、子系统越来越复杂,增加门面模式提供简单接口

2、构建多层系统结构,利用门面对象作为每层的入口,简化层间调用。

1.2 门面模式的通用写法

首先来看门面模式的UML类图:

img

门面模式主要包含2种角色:

外观角色(Facade):也称门面角色,系统对外的统一接口;

子系统角色(SubSystem):可以同时有一个或多个 SubSystem。每个 SubSytem 都不是一个单独 的类,而是一个类的集合。 SubSystem 并不知道 Facade 的存在,对于 SubSystem 而言, Facade 只 是另一个客户端而已(即 Facade 对 SubSystem 透明)。

下面是门面模式的通用代码,首先分别创建3个子系统的业务逻辑SubSystemA、SubSystemB、 SubSystemC,代码很简单:

// 子系统
public class SubSystemA {
    public void doA() {
        System.out.println("doing A stuff");
    }
}

复制

// 子系统
public class SubSystemB {
    public void doB() {
        System.out.println("doing B stuff");
    }
}

复制

// 子系统
public class SubSystemC {
    public void doC() {
        System.out.println("doing C stuff");
    }
}

复制

来看客户端代码:

// 外观角色 Facade
public class Facade {
    private SubSystemA a = new SubSystemA();
    private SubSystemB b = new SubSystemB();
    private SubSystemC c = new SubSystemC();

    // 对外接口
    public void doA() {
        this.a.doA();
    }

    // 对外接口
    public void doB() {
        this.b.doB();
    }

    // 对外接口
    public void doC() {
        this.c.doC();
    }
}

1.3 门面模式业务场景实例

Gper社区上线了一个积分兑换礼品的商城,这礼品商城中的大部分功能并不是全部重新开发的,而是要去对接已有的各个子系统(如下图所示):

img

这些子系统可能涉及到积分系统、支付系统、物流系统的接口调用。如果所有的接口调用全部由前 端发送网络请求去调用现有接口的话,一则会增加前端开发人员的难度,二则会增加一些网络请求影响 页面性能。这个时候就可以发挥门面模式的优势了。将所有现成的接口全部整合到一个类中,由后端提 供统一的接口给前端调用,这样前端开发人员就不需要关心各接口的业务关系,只需要把精力集中在页 面交互上。下面我们用代码来模拟一下这个场景。

首先,创建礼品的实体类GiftInfo:

public class GiftInfo {

    private String name;

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

    public String getName() {
        return name;
    }
}

然后,编写各个子系统的业务逻辑代码,分别创建积分系统QualifyService类:

public class QualifyService {

    public boolean isAvailable(GiftInfo giftInfo){
        System.out.println("校验" +giftInfo.getName() + "积分通过,库存通过。");
        return true;
    }
}

支付系统PaymentService类:

public class PaymentService {

    public boolean pay(GiftInfo giftInfo) {
        System.out.println("扣减" + giftInfo.getName() + " 积分成功");
        return true;
    }
}

物流系统ShippingService类:

public class ShippingService {
    public String delivery(GiftInfo giftInfo){
        System.out.println(giftInfo.getName() + "进入物流系统");
        String shippingNo = "666";
        return shippingNo;
    }
}

然后创建外观角色GiftFacdeService 类,对外只开放一个兑换礼物的exchange()方法,在 exchange() 方法内部整合3个子系统的所有功能。

public class FacadeService {
    private QualifyService qualifyService = new QualifyService();
    private PaymentService paymentService = new PaymentService();
    private ShippingService shippingService = new ShippingService();

    //兑换
    public void exchange(GiftInfo giftInfo){
        //资格校验通过
        if(qualifyService.isAvailable(giftInfo)){
            //如果支付积分成功
            if(paymentService.pay(giftInfo)){
                String shippingNo = shippingService.delivery(giftInfo);
                System.out.println("物流系统下单成功,物流单号是:" + shippingNo);
            }
        }
    }
}

最后,来看客户端代码:

public class Test {
    public static void main(String[] args) {
        FacadeService facadeService = new FacadeService();
        GiftInfo giftInfo = new GiftInfo("《Spring 5核心原理》");
        facadeService.exchange(giftInfo);
    }
}

运行结果如下:

校验《Spring 5核心原理》积分通过,库存通过。
扣减《Spring 5核心原理》 积分成功
《Spring 5核心原理》进入物流系统
物流系统下单成功,物流单号是:666

通过这样一个案例对比之后,相信大家对门面模式的印象非常深刻了。

1.4 门面模式在源码中的应用

下面我们来门面模式在源码中的应用,先来看Spring JDBC模块下的JdbcUtils类,它封装了和 JDBC相关的所有操作,它一个代码片段:

public abstract class JdbcUtils {

	/**
	 * Constant that indicates an unknown (or unspecified) SQL type.
	 * @see java.sql.Types
	 */
	public static final int TYPE_UNKNOWN = Integer.MIN_VALUE;


	private static final Log logger = LogFactory.getLog(JdbcUtils.class);


	/**
	 * Close the given JDBC Connection and ignore any thrown exception.
	 * This is useful for typical finally blocks in manual JDBC code.
	 * @param con the JDBC Connection to close (may be {@code null})
	 */
	public static void closeConnection(Connection con) {
		if (con != null) {
			try {
				con.close();
			}
			catch (SQLException ex) {
				logger.debug("Could not close JDBC Connection", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JDBC driver: It might throw RuntimeException or Error.
				logger.debug("Unexpected exception on closing JDBC Connection", ex);
			}
		}
	}

	/**
	 * Close the given JDBC Statement and ignore any thrown exception.
	 * This is useful for typical finally blocks in manual JDBC code.
	 * @param stmt the JDBC Statement to close (may be {@code null})
	 */
	public static void closeStatement(Statement stmt) {
		if (stmt != null) {
			try {
				stmt.close();
			}
			catch (SQLException ex) {
				logger.trace("Could not close JDBC Statement", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JDBC driver: It might throw RuntimeException or Error.
				logger.trace("Unexpected exception on closing JDBC Statement", ex);
			}
		}
	}

	/**
	 * Close the given JDBC ResultSet and ignore any thrown exception.
	 * This is useful for typical finally blocks in manual JDBC code.
	 * @param rs the JDBC ResultSet to close (may be {@code null})
	 */
	public static void closeResultSet(ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			}
			catch (SQLException ex) {
				logger.trace("Could not close JDBC ResultSet", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JDBC driver: It might throw RuntimeException or Error.
				logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
			}
		}
	}
    ...
}

其他更多的操作,看它的结构就非常清楚了:

img

再来看一个MyBatis中的Configuration类。它其中有很多new开头的方法,来看一下源代码:

public MetaObject newMetaObject(Object object) {
  return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
    ResultHandler resultHandler, BoundSql boundSql) {
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
  return newExecutor(transaction, defaultExecutorType);
}

上面的这些方法都是对JDBC中关键组件操作的封装。另外地在Tomcat的源码中也有体现,也非常的 有意思。举个例子RequestFacade类,来看源码:

@SuppressWarnings("deprecation")
public class RequestFacade implements HttpServletRequest {
	...
    @Override
    public String getContentType() {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        return request.getContentType();
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        return request.getInputStream();
    }


    @Override
    public String getParameter(String name) {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        if (Globals.IS_SECURITY_ENABLED){
            return AccessController.doPrivileged(
                new GetParameterPrivilegedAction(name));
        } else {
            return request.getParameter(name);
        }
    }
    ...
}

我们看名字就知道它用了门面模式。它封装了非常多的request的操作,也整合了很多servlet-api以 外的一些内容,给用户使用提供了很大便捷。同样,Tomcat对Response和Session当也封装了 ResponseFacade和StandardSessionFacade类,感兴趣的小伙伴可以去深入了解一下。

1.5 门面模式的优缺点

优点:

1、简化了调用过程,无需深入了解子系统,以防给子系统带来风险。

2、减少系统依赖、松散耦合

3、更好地划分访问层次,提高了安全性

4、遵循迪米特法则,即最少知道原则。

缺点:

1、当增加子系统和扩展子系统行为时,可能容易带来未知风险

2、不符合开闭原则

3、某些情况下可能违背单一职责原则。

2 装饰器模式

2.1 装饰器模式定义

装饰器模式(Decorator Pattern),也称为包装模式(Wrapper Pattern)是指在不改变原有对象 的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于 结构型模式。

原文:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality. 解释:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

装饰器模式的核心是功能扩展。使用装饰器模式可以透明且动态地扩展类的功能。

装饰器模式主要用于透明且动态地扩展类的功能。其实现原理为:让装饰器实现被包装类(Concrete Component)相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入 该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加 新功能了。而且由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实 现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一 层的对最底层被包装类进行功能扩展了。

首先看下装饰器模式的通用UML类图:

从 UML 类图中,我们可以看到,装饰器模式 主要包含四种角色:

img

抽象组件(Component):可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被 装饰对象的行为;

具体组件(ConcreteComponent):实现/继承Component的一个具体对象,也即被装饰对象;

抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性 指向 Component抽象组件;其实现一般是一个抽象类,主要是为了让其子类按照其构造形式传入一 个 Component 抽象组件,这是强制的通用行为(当然,如果系统中装饰逻辑单一,并不需要实现许 多装饰器,那么我们可以直接省略该类,而直接实现一个具体装饰器(ConcreteDecorator)即可);

具体装饰器(ConcreteDecorator):Decorator 的具体实现类,理论上,每个 ConcreteDecorator 都扩展了Component对象的一种功能;

总结:装饰器模式角色分配符合设计模式里氏替换原则,依赖倒置原则,从而使得其具备很强的扩展性,最终满足开 闭原则。

2.2 装饰器模式的应用场景

装饰器模式在我们生活中应用也比较多如给煎饼加鸡蛋;给蛋糕加上一些水果;给房子装修等,为对象扩展一些额外的职责。装饰器在代码程序中适用于以下场景:

1、用于扩展一个类的功能或给一个类添加附加职责。

2、动态的给一个对象添加功能,这些功能可以再动态的撤销。

3、需要为一批的兄弟类进行改装或加装功能。

来看一个这样的场景,上班族白领其实大多有睡懒觉的习惯,每天早上上班都是踩点,于是很多小 伙伴为了多赖一会儿床都不吃早餐。那么,也有些小伙伴可能在上班路上碰到卖煎饼的路边摊,都会顺 带一个到公司茶水间吃早餐。卖煎饼的大姐可以给你的煎饼加鸡蛋,也可以加香肠(如下图,PS:我买 煎饼一般都要求不加生菜)。

img

下面我们用代码还原一下码农的生活。首先创建一个煎饼Battercake类:

public class Battercake {

    protected String getMsg(){ return "煎饼";}

    public int getPrice(){ return 5;}
}

创建一个加鸡蛋的煎饼BattercakeWithEgg类:

public class BattercakeWithEgg extends Battercake {
	@Override 
    protected String getMsg(){ return super.getMsg() + "+1个鸡蛋";}

    @Override 
    //加一个鸡蛋加 1 块钱 
    public int getPrice(){ return super.getPrice() + 1;}
}

再创建一个既加鸡蛋又加香肠的BattercakeWithEggAndSausage类:

public class BattercakeWithEggAndSauage extends BattercakeWithEgg {
    @Override
    protected String getMsg(){ return super.getMsg() + "+1根香肠";}

    @Override
    //加一个香肠加 2 块钱
    public int getPrice(){ return super.getPrice() + 2;}
}

编写客户端测试代码:

public class Test {
    public static void main(String[] args) {
        Battercake battercake = new Battercake();
        System.out.println(battercake.getMsg() + ",总价:" + battercake.getPrice());

        BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg.getMsg() + ",总价:" + battercakeWithEgg.getPrice());

        BattercakeWithEggAndSauage battercakeWithEggAndSauage = new BattercakeWithEggAndSauage();
        System.out.println(battercakeWithEggAndSauage.getMsg() + ",总价:" + battercakeWithEggAndSauage.getPrice());
    }
}

运行结果:

煎饼,总价:5
煎饼+1个鸡蛋,总价:6
煎饼+1个鸡蛋+1根香肠,总价:8

运行结果没有问题。

但是,如果用户需要一个加2个鸡蛋加1根香肠的煎饼,那么用我们现在的类 结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。如果需求再变,一直加定制 显然是不科学的。那么下面我们就用装饰器模式来解决上面的问题。

首先创建一个建煎饼的抽象 Battercake类:

public abstract class Battercake {

    protected abstract String getMsg();

    protected abstract int getPrice();
}

创建一个基本的煎饼(或者叫基础套餐)BaseBattercake:

public class BaseBattercake extends Battercake{

    protected String getMsg(){ return "煎饼";}

    public int getPrice(){ return 5;}
}

然后,再创建一个扩展套餐的抽象装饰器BattercakeDecotator类:

public class BattercakeDecorator extends Battercake{
    //静态代理,委派 
    private Battercake battercake;

    public BattercakeDecorator(Battercake battercake) {
        this.battercake = battercake;
    }
    
	@Override 
    protected String getMsg(){ return this.battercake.getMsg();}
    
	@Override 
    public int getPrice(){ return this.battercake.getPrice();}
}

然后,创建鸡蛋装饰器EggDecorator类:

public class EggDecorator extends BattercakeDecorator{

    public EggDecorator(Battercake battercake) {
        super(battercake);
    }

    @Override
    protected String getMsg(){ return super.getMsg() + "1个鸡蛋";}

    @Override
    public int getPrice(){ return super.getPrice() + 1;}
}

创建香肠装饰器SausageDecorator类:

public class SauageDecorator extends BattercakeDecorator{

    public SauageDecorator(Battercake battercake) {
        super(battercake);
    }

    protected String getMsg(){ return super.getMsg() + "1根香肠";}

    public int getPrice(){ return super.getPrice() + 2;}
}

编写客户端测试代码:

public class Test {
    public static void main(String[] args) {
        //路边摊买一个煎饼
        Battercake battercake = new BaseBattercake();
        //煎饼有点小,想再加一个鸡蛋
        battercake = new EggDecorator(battercake);
        //再加一个鸡蛋
        battercake = new EggDecorator(battercake);
        //很饿,再加根香肠
        battercake = new SauageDecorator(battercake);
        //跟静态代理最大区别就是职责不同 
        //静态代理不一定要满足 is-a 的关系 
        //静态代理会做功能增强,同一个职责变得不一样
        //装饰器更多考虑是扩展 
        System.out.println(battercake.getMsg() + ",总价" + battercake.getPrice());
    }
}

运行结果:

煎饼,总价:5
煎饼+1个鸡蛋,总价:6
煎饼+1个鸡蛋+1根香肠,总价:8

来看一下类图:

img

为了加深印象,我们再来看一个应用场景。需求大致是这样,系统采用的是sls服务监控项目日志, 以Json的格式解析,所以需要将项目中的日志封装成json格式再打印。现有的日志体系采用了log4j+ slf4j框架搭建而成。客户端调用是这样的:

private static final Logger logger = LoggerFactory.getLogger(Component.class); 
logger.error(string);

这样打印出来的是毫无规则的一行行字符串。在考虑将其转换成json格式时,我采用了装饰器模式。 目前有的是统一接口 Logger 和其具体实现类,我要加的就是一个装饰类和真正封装成 Json格式的装 饰产品类。创建装饰器类LoggerDecorator:

public class LoggerDecorator implements Logger {

    protected Logger logger;

    public LoggerDecorator(Logger logger) {
        this.logger = logger;
    }

	public void error(String s) {
    }

    public void error(String s, Object o) {
    }
    //省略其他默认实现
}

创建具体组件JasonLogger类实现代码如下:

public class JsonLogger extends LoggerDecorator {
    public JsonLogger(Logger logger) {
        super(logger);
    }

    @Override
    public void info(String s) {
        JSONObject result = newJsonObject();
        result.put("message",s);
        logger.info(result.toString());
    }

    @Override
    public void error(String s) {
        JSONObject result = newJsonObject();
        result.put("message",s);
        logger.info(result.toString());
    }

    public void error(Exception e){
        JSONObject result = newJsonObject();
        result.put("exception",e.getClass().getName());
        String trace = Arrays.toString(e.getStackTrace());
        result.put("starckTrace",trace);
        logger.info(result.toString());
    }

    private JSONObject newJsonObject(){
        //拼装了一些运行时信息
        return new JSONObject();
    }
}

可以看到,在JsonLogger中,对于Logger的各种接口,我都用JsonObject对象进行一层封装。 在打印的时候,最终还是调用原生接口 logger.error(string),只是这个 string 参数已经被我们装饰过 了。如果有额外的需求,我们也可以再写一个函数去实现。比如 error(Exception e),只传入一个异常 对象,这样在调用时就非常方便了。

另外,为了在新老交替的过程中尽量不改变太多的代码和使用方式。我又在JsonLogger 中加入了 一个内部的工厂类JsonLoggerFactory(这个类转移到DecoratorLogger 中可能更好一些),他包含 一个静态方法,用于提供对应的JsonLogger实例。最终在新的日志体系中,使用方式如下:

public class Test {
    private static final Logger logger = JsonLoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        logger.error("系统错误");
    }
}

对于客户端而言,唯一与原先不同的地方就是将 LoggerFactory 改为JsonLoggerFactory 即可, 这样的实现,也会被更快更方便的被其他开发者接受和习惯。最后看一下类图:

img

装饰器模式最本质的特征是讲原有类的附加功能抽离出来,简化原有类的逻辑。通过这样两个案例,我们可以总结出来,其实抽象的装饰器是可有可无的,具体可以根据业务模型来选择。

2.3 装饰器模式在源码中的应用

装饰器模式在源码中也应用得非常多,在 JDK 中体现最明显的类就是 IO 相关的类,如 BufferedReader、InputStream、OutputStream,看一下常用的InputStream的类结构图:

img

在Spring 中的 TransactionAwareCacheDecorator类我们也可以来尝试理解一下,这个类主要是用来处理事务缓存的,来看一下代码:

public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;

    public TransactionAwareCacheDecorator(Cache targetCache) {
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }

    public Cache getTargetCache() {
        return this.targetCache;
    }
    ...
}

TransactionAwareCacheDecorator就是对Cache的一个包装。再来看一个 MVC中的装饰器模式 HttpHeadResponseDecorator类:

public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
    public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
        super(delegate);
    }
    ...
}

最后,看看 MyBatis中的一段处理缓存的设计 org.apache.ibatis.cache.Cache 类,找到它的包定 位:

在这里插入图片描述

从名字上来看其实更容易理解了。比如 FifoCache先入先出算法的缓存;LruCache最近最少使用 的缓存;TransactionlCache事务相关的缓存,都是采用装饰器模式。MyBatis源码在我们后续的课程 也会深入讲解,感兴趣的小伙伴可以详细看看这块的源码,也可以好好学习一下MyBatis的命名方式, 今天我们还是把重点放到设计模式上。

2.4 装饰器模式和代理模式对比

从代理模式的 UML类图和通用代码实现上看,代理模式与装饰器模式几乎一模一样。代理模式的 Subject 对 应 装 饰 器 模 式 的 Component , 代 理 模 式 的 RealSubject 对 应 装 饰 器 模 式 的 ConcreteComponent,代理模式的 Proxy对应装饰器模式的Decorator。确实,从代码实现上看,代 理模式的确与装饰器模式是一样的(其实装饰器模式就是代理模式的一个特殊应用),但是这两种设计 模式所面向的功能扩展面是不一样的:

装饰器模式强调自身功能的扩展。Decorator所做的就是增强ConcreteComponent的功能(也有 可能减弱功能),主体对象为ConcreteComponent,着重类功能的变化;

代理模式强调对代理过程的控制。Proxy 完全掌握对 RealSubject的访问控制,因此,Proxy 可以决定对RealSubject 进行功能扩展,功能缩减甚至功能散失(不调用RealSubject方法),主体对象为 Proxy;

简单来讲,假设现在小明想租房,那么势必会有一些事务发生:房源搜索,联系房东谈价格····

假设我们按照代理模式进行思考,那么小明只需找到一个房产中介,让他去干房源搜索,联系房东 谈价格这些事情,小明只需等待通知然后付点中介费就行了;

而如果采用装饰器模式进行思考,因为装饰器模式强调的是自身功能扩展,也就是说,如果要找房 子,小明自身就要增加房源搜索能力扩展,联系房东谈价格能力扩展,通过相应的装饰器,提升自身能 力,一个人做满所有的事情。

2.5 装饰器模式的优缺点

优点:

1、装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。

2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。

3、装饰器完全遵守开闭原则。

缺点:

1、会出现更多的代码,更多的类,增加程序复杂性。

2、动态装饰时,多层装饰时会更复杂。

那么装饰器模式我们就讲解到这里,希望小伙伴们认真体会,加深理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/351540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git命令篇(持续更新中)

首先介绍这个网页&#xff1a;https://learngitbranching.js.org/?localezh_CN --提交命令 git commit --创建分支 git branch <分支名> --切换分支 git checkout <分支名> --合并分支 (合并到主分支去&#xff0c;把我合并到谁的身上去) 自己写的分支合并到主线…

Unity—游戏设计模式+GC

每日一句&#xff1a;"少年一贯快马扬帆 道阻且长不转弯 要盛大要绚烂要哗然 要用理想的泰坦尼克去撞现实的冰川 要当烧赤壁的风而非借箭的草船 要为一片海就肯翻万山。" 目录 状态模式&#xff1a; 外观模式 组合模式&#xff0c; 单例模式 命令模式 观察者模…

FPGA开发软件(vivado + modelsim)环境搭建(附详细安装步骤+软件下载)

本文详细介绍了vivado软件和modelsim软件的安装&#xff0c;以及vivado中配置modelsim仿真设置&#xff0c;每一步都加文字说明和图片。一、软件安装包下载1、vivado vivado版本很多&#xff0c;目前最新的已更新到vivado2022.2&#xff0c;版本越高&#xff0c;安装包越大&…

90后阿里P7技术专家晒出工资单:狠补了这个,真香...

最近一哥们跟我聊天装逼&#xff0c;说他最近从阿里跳槽了&#xff0c;我问他跳出来拿了多少&#xff1f;哥们表示很得意&#xff0c;说跳槽到新公司一个月后发了工资&#xff0c;月入5万多&#xff0c;表示很满足&#xff01;这样的高薪资着实让人羡慕&#xff0c;我猜这是税后…

全局快门和卷帘快门(Global shutter and Rolling shutter)

全局快门和卷帘快门 “果冻效应”是什么&#xff1f;用相机拍下扇叶&#xff0c;为什么会发生扭曲变形&#xff1f; 相机两种曝光方式的优劣&#xff1a;全局曝光和卷帘曝光 卷帘快门和全局快门的区别 全局曝光和卷帘曝光是常见的相机曝光方式 一般来说&#xff0c;CCD相机是全…

【3D目标检测】Fastpillars-2023论文

论文&#xff1a;fastpillars.pdf https://arxiv.org/abs/2302.02367 作者&#xff1a;东南大学&#xff0c;美团 代码&#xff1a;https://github.com/StiphyJay/FastPillars &#xff08;暂未开源&#xff09; 讲解&#xff1a;https://mp.weixin.qq.com/s/ocNH2QBoD2AeK-…

「JVM 编译优化」javac 编译器源码解读

Java 的编译过程 前端编译: 编译器的前端&#xff0c;将 Java 文件转变成 Class 文件的过程&#xff1b;如 JDK 的 javac、Eclipse JDT 中的增量式编译器 ECJ&#xff1b;即使编译: JIT&#xff0c;Just In Time Compiler&#xff0c;在运行期将字节码转变成本地机器码的过程&…

COSELF 次元秀场伦敦时装周预告 #虚拟时尚

在虚拟世界里的未来服装&#xff0c;能自由变化自己的样貌和服饰。或许未来会作为人类皮肤的第二表征&#xff0c;极大解放人们的精神自由。COSELF 次元秀场 「预告」数字高定系列时间&#xff1a;本月 17 - 21 日地点&#xff1a;当季伦敦时装周「COSELF 次元秀场-数字高定系列…

excel图片技巧:如何为报表配上节日祝福动画

偶尔跳跃一下&#xff0c;改变一下&#xff0c;哪怕被说成是“拍马屁”也行&#xff0c;因为&#xff0c;快乐、传递快乐是一种幸福&#xff0c;是内心本身就有的欲望。提升自己在同事和领导心里的形象只是传递快乐的附加值。圣诞节就快到了&#xff0c;发送报表的时候附带一个…

vue的组件通信

文章目录3. 组件通信3.1 父组件-->子组件3.3组件自定义事件&#xff08;子->父&#xff09;3.4.全部事件总线&#xff08;两代以上&#xff09;3.5消息的订阅与发布3. 组件通信 3.1 父组件–>子组件 <Student name"张三" :age"18"></St…

Java Excel的数据导入导出

引入依赖 <!-- EasyExcel --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.7</version> </dependency><!--csv文件操作--> <dependency><groupId>n…

将ChatGPT整合到Word中

引言自ChatGPT出现&#xff0c;各种基于它的软件扩展纷至沓来&#xff0c;目前Word支持ChatGPT的add-in有两款&#xff0c;可以通过:插入->获取加载项->搜索openai查看。其中Ghostwriter从介绍上看功能比较单一&#xff0c;而且软件需要购买&#xff0c;用自己的API-key&…

php5.6.9安装sqlsrv扩展(windows)

报错:Marning: PHP Startup: Unable to load dynamic 1library D:lphpstudy_prolExtensionslphpl(phps.6.9ntslextphp_ pdo_sqlsry 56 nts′找不到指定的模块。in Unknown on line 0 整整搞了一天才终于解决 我用的是phpstudy_pro&#xff08;也就是小皮v8.1版本&#xff09;&…

Twitter多账号想要做到防关联?还是得靠它

接着上一篇Twitter养号攻略的文章&#xff0c;这篇龙哥就来详细讲讲当批量注册和管理Twitter账号时需要怎么防关联。 Twitter作为海外最流行的社交网站之一&#xff0c;它拥有很庞大的用户量&#xff0c;所以很多跨境电商都会通过Twitter来投放广告、推广自己的产品、提高曝光度…

pdf生成为二维码

当今数字时代&#xff0c;人们越来越依赖在线工具来处理各种任务&#xff0c;比如合并、拆分和压缩PDF等。Mai File就是这样一个在线工具&#xff0c;它可以将PDF文件转换成在线链接&#xff0c;方便您和他人轻松地查看和共享文件。 Mai File的使用非常简单&#xff0c;您只需…

从GPT到chatGPT(三):GPT3(二)

GPT3&#xff08;二&#xff09; 前言 因为上一篇文章 从GPT到chatGPT&#xff08;三&#xff09;&#xff1a;GPT3&#xff08;一&#xff09;阅读量挺高&#xff0c;给了我继续更新对该论文进行解读的莫大动力。这篇文章主要讲原论文的第三章&#xff08;Results&#xff0…

Vue2快速入门(三)前端项目架构搭建、Axios、Vue-Router

文章目录VueCli 4.3搭建前端项目架构创建vue项目前端项目目录结构添加axios添加cube-ui依赖创建新目录http客户端Axios什么是Axios&#xff1f;GET请求方式POST请求方式Axios封装通用后端请求API模块Vue-Router开发前端项目路由什么是vue-router&#xff1f;配置项目路由VueCli…

Hive SQL语言:DDL建库、建表

Hive SQL语言&#xff1a;DDL建库、建表 Hive数据模型总览 Hive SQL之数据库与建库 SQL中DDL语法的作用 ⚫ 数据定义语言(Data Definition Language, DDL)&#xff0c;是SQL语言集中对数据库内部的对象结构进行创建&#xff0c;删除&#xff0c;修改等的操作语言&#xff…

《反电信网络诈骗法》实行,Galaxybase图平台成为电信反诈黑科技

电信网络诈骗在当前的数字化生活中始终是一个高频讨论词。 近年来&#xff0c;随着互联网技术发展迅速&#xff0c;线上交易趋于频繁化&#xff0c;以电信网络诈骗为代表的新型网络犯罪行为也变得越来越高发。根据中国信通院《新形势下电信网络诈骗治理研究报告&#xff08;20…

交互式推荐在外卖场景的探索与应用

外卖场景的用户停留时长低于传统电商&#xff0c;对用户实时需求的理解和反馈有更高的要求。针对业务问题&#xff0c;外卖推荐团队从2021年起开始持续投入&#xff0c;最终摸索出了一套适用于外卖场景的交互式推荐架构和策略&#xff0c;并取得了较好的收益。下文将详细介绍外…