我能正确理解装饰器模式吗?

Edw*_*win 2 java design-patterns decorator

我正在学习装饰模式.我写了这个程序来测试我的知识.我做对了吗?

public interface Logger {
    void log(String msg);
}

public class BasicLogger implements Logger {

    @Override
    public void log(String msg) {
        System.out.println("BasicLogger: " + msg);
    }
}
Run Code Online (Sandbox Code Playgroud)

这里是我开始感到困惑的地方,如果我要在HTMLLogger课程中覆盖它,那么装饰器中的logger.log(msg)有什么意义呢?

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

    @Override
    public void log(String msg) {
        logger.log(msg);
    }
}
Run Code Online (Sandbox Code Playgroud)

我甚至支持复制该logger.log(msg);行(或者装饰者有什么)?

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

    @Override
    public void log(String msg) {
        logger.log(msg);
        System.out.println("<HTML> HTML Logger" + msg);
        //Generate the html file
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在演示类中我有这个:

public LoggerTest() {
    BasicLogger logger = new BasicLogger();
    Logger htmlLogger = new HTMLLogger(new BasicLogger());
    logger.log("Basic Logger log");
    htmlLogger.log("HTML Logging");
}
Run Code Online (Sandbox Code Playgroud)

输出是:

BasicLogger: Basic Logger log
BasicLogger: HTML Logging
<HTML> HTML LoggerHTML Logging
Run Code Online (Sandbox Code Playgroud)

我真的需要对装饰器模式有充分的了解,因为我需要使用AspectJ来实现它.

jmo*_*253 6

您可能缺少的是一条评论/* your code here */,说明了在进行子类化时如何做的说明.

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

    @Override
    public void log(String msg) {

        /** YOUR CODE HERE WHEN SUBCLASSING **/

        /**
         * delegate to the base logger passed into the constructor to
         * perform existing logging operations.
         */
        logger.log(msg);
    }
}
Run Code Online (Sandbox Code Playgroud)

每次调用装饰器构造函数时,都会传入一个现有的记录器.此记录器分配给私有变量,并在重写的日志方法中使用.对于每个装饰,您都可以为logger.log调用添加更多功能.

例如,当您实例化HTMLLogger时,您将添加system.out消息.作为练习,创建另一个名为XMLLogger的具体记录器,如下所示:

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

    @Override
    public void log(String msg) {
        logger.log(msg);
        System.out.println("<?xml version="1.0"?><message>XML Logger" + msg);
        //Generate the xml file
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将其添加到您的测试运行器:

public LoggerTest() {
    BasicLogger logger = new BasicLogger();
    Logger htmlLogger = new HTMLLogger(new BasicLogger());
    Logger xmlAndHtmlLogger = new XMLLogger(new HTMLLogger());
    logger.log("Basic Logger log");
    htmlLogger.log("HTML Logging");
    xmlAndHtmlLogger.log("I am Both HTML and XML logging");
}
Run Code Online (Sandbox Code Playgroud)

输出:

BasicLogger: Basic Logger log
BasicLogger: HTML Logging
<HTML> HTML LoggerHTML Logging

BasicLogger: I am Both HTML and XML logging
<HTML> HTML LoggerI am Both HTML and XML logging
<?xml version="1.0"?><message>XML LoggerI am Both HTML and XML logging
Run Code Online (Sandbox Code Playgroud)

在上面的输出中,我插入了一个空格,只是为了显示xmlAndHtmlLogger.log方法调用产生的输出.

装饰器模式通过扩展基类,向重写方法添加其他代码,然后委托回原始方法来工作.因此,对于您实例化的每个新装饰器子类,您将为该重写方法添加更多功能.

由于xmlAndHtmlLogger使用HTMLLogger进行了装饰,而HTMLLogger是从基类创建的,因此我们在调用方法时获得了所有这三种功能.您可以按任何顺序混合和匹配这些装饰器调用,以确定功能的顺序,甚至通过省略其中一个装饰器来省略某些功能.


装饰模式的优点

我想澄清一下,装饰器模式的优点是你可以创建具有不同混合和匹配功能的不同对象组合,而不必为每个组合创建N个具体子类.在此示例中,使用BaseLogger,HTMLLogger和XMLLogger,我们可以创建以下对象:

  • 一个没有任何想象力的基础记录器.
  • HTML记录器
  • XML记录器
  • 一个也处理XML的HTML记录器!

第四项很重要,因为它是两种装饰的组合.假设我们还添加了以下附加装饰器:

  • JSONLogger
  • YAMLLogger

通过这两个额外的装饰器,我们现在可以在运行时创建以下对象:

  • 没有花哨的基础记录器.
  • HTMLLogger
  • XMLLogger
  • HTML和XML Logger合二为一
  • HTML和JSON Logger合二为一
  • HTML和YAML Logger合二为一
  • HTML和XML以及JSON Logger合二为一
  • HTML和XML以及JSON和YAML合二为一
  • 等等

简而言之,我们不是为每个所需的组合创建一个具体的子类,而是简单地创建具有基本功能的简单类,然后通过将创建的对象链接到下一个对象的构造函数中,在运行时添加额外的功能.

因此,4个装饰器可以产生超过16种不同的记录器组合!这是一个强大的概念,可以节省大量的编码时间.

有关更深入的示例,请参阅Wikipedia的WindowScrolling示例以及Coffee示例.记下测试运行器中的构造函数,您可以清楚地看到每个对象传递回下一个类的构造函数.这是"装饰"一个物体的过程.