【问题标题】:Am I understanding the Decorator pattern correctly?我是否正确理解了装饰器模式?
【发布时间】:2014-10-17 22:54:19
【问题描述】:

我目前正在学习装饰器模式。我写了这个程序来测试我的知识。我说对了吗?

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

public class BasicLogger implements Logger {

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

这是我开始感到困惑的地方,如果我要在 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);
    }
}

我什至应该复制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
    }
}

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

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

输出是:

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

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

【问题讨论】:

  • 是的,你说得对。在一定程度上。通常在您的装饰器中,甚至在装饰器的子级中,您希望在让工作发生在超类中之前放入某种类型的逻辑。例如,在您的 HTML 中,您可能想要搜索非 HTML 友好字符并替换它们。或者添加验证,或者支持多个输出目的地。

标签: java design-patterns decorator


【解决方案1】:

您可能缺少一条注释,上面写着 /* 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);
    }
}

每次调用装饰器构造函数时,都会传入一个现有的记录器。这个记录器被分配给一个私有变量,并在被覆盖的日志方法中使用。对于每个装饰,您都可以向 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
    }
}

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

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

输出:

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

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

装饰器模式的工作原理是扩展一个基类,向被覆盖的方法添加额外的代码,然后委托回原来的方法。因此,对于您实例化的每个新装饰器子类,您都会向该重写方法添加更多功能。

由于 xmlAndHtmlLogger 是用 HTMLLogger 装饰的,它是从基础子类化的,所以在调用该方法时我们可以获得所有三个功能。您可以按任何顺序混合和匹配这些装饰器调用,以确定功能的顺序,甚至通过省略其中一个装饰器来省略某些功能。


装饰器模式的优点

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

  • 一个没有什么花哨的基本记录器。
  • HTML 记录器
  • XML 记录器
  • 一个同时处理 XML 的 HTML 记录器!

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

  • JSONLogger
  • YAMLLogger

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

  • 没有什么花哨的基础记录器。
  • HTMLLogger
  • XMLLogger
  • HTML 和 XML 记录器合二为一
  • HTML 和 JSON 记录器合二为一
  • HTML 和 YAML 记录器合二为一
  • HTML、XML 和 JSON 记录器合二为一
  • HTML 和 XML 以及 JSON 和 YAML 合二为一

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

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

有关更深入的示例,请参阅Wikipedia's WindowScrolling Example 以及咖啡示例。记下测试运行器中的构造函数,您可以清楚地看到每个对象都传回了下一个类的构造函数。这是“装饰”对象的过程。

【讨论】:

    【解决方案2】:

    装饰器只是一个包装器。动作(方法)的“包装”允许您通过在动作发生之前/之后应用各种事物来“干预”动作。

    记住这一点,让我们以您的第一个示例为例并对其进行修改以实现装饰器模式(我们可以应用不同的实现)。

    使用继承:

    public class Logger {
        void log(String msg){
            System.out.println("msg = " + msg);
        };
    }
    
    public class BasicLogger extends Logger {
    
        @Override
        public void log(String msg) {
            System.out.println("BasicLogger - before logging: " + msg);
            super.log(msg);
            System.out.println("BasicLogger - after logging: " + msg);
        }
    } 
    

    使用组合:

    public class Logger {
        void log(String msg){
            System.out.println("msg = " + msg);
        };
    }
    
    public class BasicLogger {
    
        Logger logger;
    
        public BasicLogger(Logger logger) {
            this.logger = logger;
        }
    
        public void log(String msg) {
            System.out.println("BasicLogger - before logging: " + msg);
            logger.log(msg);
            System.out.println("BasicLogger - after logging: " + msg);
        }
    } 
    

    【讨论】:

    • 嗨 alfasin,如何使用继承对记录器应用不同的装饰?例如,如果我有一个 HTMLLogger、BaseLogger 和 XMLLogger,使用组合,我可以使用这三个装饰器的任意组合创建对象。但是有了继承,怎么才能混搭不同的装饰呢?除非我遗漏了什么,否则我可以看到装饰器工作的唯一方法是使用组合方法。您介意展示一些使用继承进行实例化的示例吗?如果我对此有误,我很想看看它是如何完成的,它也会对其他人有所帮助。谢谢! :)
    • 在仔细研究之后,我认为这是不正确的。必须使用组合来正确实现装饰器模式。请参阅Horizontal and Vertical scrollbar example in Wikipedia 及其下方的 Coffee 示例。这两个示例都演示了如何实例化一个基础,然后通过将前一个对象添加到下一个对象的构造函数中来添加更多功能。装饰器就是在运行时而不是编译时扩展。希望这会有所帮助!
    • @jmort253 感谢您的链接!顶部的定义说:“......静态或动态......”这意味着模式的编译时(静态/组合)和运行时(动态/继承)实现都是可以接受的。你指出我的例子是使用继承,我同意你的观点,甚至更进一步说我宁愿使用组合而不是继承,大多数实现几乎......任何东西:) 谢谢!
    • 我在答案的底部进一步解决了这个问题。装饰器模式的优点是我可以编写,比如说,记录器的 4 个实现,然后在运行时通过链接实例化对象来混合和匹配功能。仅使用继承,我们不是必须为每个组合创建 16 个不同的子类吗?我认为这是我在维基百科答案中看到的主要区别和优势。如果我可以将所有内容总结为一个问题,那就是:继承装饰器与常规继承相比有什么优势?希望这有助于澄清。
    • @jmort253 我可以看到继承优于组合的唯一优势(不仅在装饰器模式实现中,而且在一般情况下)是防止代码/逻辑重复。通常你也可以通过作曲来做到这一点,但有时你不能。所以除了这些例外,我会选择你前面提到的所有优点的组合!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 2020-09-30
    • 1970-01-01
    相关资源
    最近更新 更多