【问题标题】:trying to grasp Decorator design for dynamic hierarchical class relationship试图掌握动态层次类关系的装饰器设计
【发布时间】:2017-09-01 19:08:33
【问题描述】:

我正在尝试学习装饰器设计,并且我想出了一些很棒的东西,但我不知道我的想法是否可以编译。所以我创建了一些类:

这是基类

class parameter
{
public:
    parameter(){}
    parameter(double mini, double maxi, double def) :
    mini(mini),
    maxi(maxi),
    def(def)
    {}
    double mini, maxi, def;
    double val;
    virtual double getValue() { return val; }
    virtual void setValue(double v) { val = v; }
};

这个类存储smoothedParameters。 smoothedParameter 将在需要平滑时将自己添加到 SmootherManager 中,并在完成后将其删除。

class SmootherManager
{
public:
    SmootherManager() {}
    juce::Array<smoothedParameter *> CurSmoothingList;
    void add(smoothedParameter * sp)
    {
        CurSmoothingList.addIfNotAlreadyThere(sp);
    }

    void remove(smoothedParameter * sp)
    {
        CurSmoothingList.removeFirstMatchingValue(sp);
    }

    void doSmoothing()
    {
        for (auto & sp : CurSmoothingList)
            sp->incValue();
    }
};

这个类随着时间的推移取值并输出一个平滑的值。

    class smoothedParameter : public parameter
    {
    public:
        //smoothedParameter(){}
        smoothedParameter(double smoothingSpeed, SmootherManager & manager, parameter * p) :
            smoothingSpeed(smoothingSpeed),
            manager(manager),
            p(p)
        {}

        double smoothingSpeed;
        SmootherManager & manager;
        parameter * p;

        rosic::ExponentialSmoother smoother;

        double getValue()
        {
            return smoother.getCurrentValue();
        }
        void setValue(double v)
        {
            p->setValue(v);
            smoother.setTargetValue(p->getValue());
            if (!smoother.finishedSmoothing())
                manager.add(this);
        }
        void incValue()
        {
            smoother.getSample();
            if (smoother.finishedSmoothing())
                manager.remove(this);
        }
    };

这个类接受一个值并通过修饰符列表随着时间的推移对其进行修改。

class modulatedParameter : public parameter
{
public:
    modulatedParameter(parameter * p) : p(p) {}
    juce::Array<modifier *> modulationInputs;
    parameter * p;
    double getValue()
    {
        double totalMod = 0;
        for (const auto & m : modulationInputs)
            totalMod += m->val;

        return totalMod * p->getValue();
    }
    void setValue(double v)
    {
        p->setValue(v);
    }
    void add(modifier * sp)
    {
        modulationInputs.addIfNotAlreadyThere(sp);
    }
    void remove(modifier * sp)
    {
        modulationInputs.removeFirstMatchingValue(sp);
    }
};

这就是它的工作原理。你有一个平滑器和一个调制器。如果您在调制器内部构建一个平滑器,您将得到一个平滑调制器。如果在平滑器内部构建调制器,则会得到非平滑调制器。

这是我想使用这些类的方式:

// create the smoother manager
SmootherManager smManager;

// create modulatable parameter
auto mp = new modulatedParameter(new parameter(0.0, 1.0, 0.0));

// create a smoothable parameter
auto sp = new smoothedParameter(0.01, smManager, new parameter(0.0, 1.0, 0.0));

// create a modulatable parameter where its modifiers are smoothed
auto mp_sp = new modulatedParameter(new smoothedParameter(0.01, smManager, new parameter(0.0, 1.0, 0.0)));

// create a parameter where values are smoothed, but the modulation is not
auto sp_mp = new smoothedParameter(0.01, smManager, modulatedParameter(new parameter(0.0, 1.0, 0.0)));

好的!问题来了。

modifier myMod;

// add a modifier to sp_mp, can't do it, sp_mp has no add function.
sp_mp->add(&myMod);

我正在尝试将调制器添加到平滑参数的调制参数中。我想了一个办法,但这似乎不对。

auto mp = new modulatedParameter(sp_mp->p);
mp->add(&myMod)
sp_mp = new smoothedParameter(0.01, smManager, mp));
任何时候我想添加/删除修饰符,我都必须经过几个步骤。我可以想办法解决这个问题,但我对什么是实用方法感到迷茫,因为我不知道 C++ 的所有可能性。装饰器设计的重点是对象可以具有不同的功能集。 ......似乎我需要为每个类都有一个“添加/删除”功能,这违背了这个设计的目的。

【问题讨论】:

    标签: c++ class decorator hierarchy


    【解决方案1】:

    装饰器设计的重点是对象可以有不同的集合 功能。

    不,装饰器的目的是获得灵活扩展对象基本功能的能力,同时保留其核心。通常,“灵活”一词假定使此扩展在运行时(动态)。

    同时,C++ 是静态类型语言。这意味着对象/变量的类型定义了您可以对它做什么以及您不可以做什么。 sp_mp-&gt;add(&amp;myMod); 可能的 IIF 变量sp_mp 的类型(类)具有add(...) 函数。这个决定是在编译时做出的,没有任何设计模式可以改变这个事实,只是裸露而已。 C++ 编译器不允许您调用函数/使用不属于其类型的变量的成员变量。 无论你做什么,现有类型的接口都是静态定义的。想换吗?在编译时执行。

    现在,考虑到所说的一切,我们可以得出一个合乎逻辑的结论:
    如果您想向现有类型添加一些新功能 - 创建一个新类型。

    这是一个或多或少经典的(我相信)装饰器实现。
    *我没有使用共享指针只是因为... OP 也没有使用它们:)

    class ICore
    {
    public:
        virtual std::string Description() = 0;
    
        void Describe() {
            std::cout << "I am " << Description() << std::endl;
        }
    };
    
    class Core final : public ICore
    {
    public:
        std::string Description() override {
            return "Core";
        }
    };
    
    class IDecorator : public ICore
    {
    protected:
        ICore* core;
    
    public:
        IDecorator(ICore* _core)
            : core{ _core }
        { }
    
        virtual ~IDecorator() {
            delete core;
        }
    };
    
    class Beautiful final : public IDecorator
    {
    public:
        Beautiful(ICore* _core)
            : IDecorator{ _core }
        { }
    
    public:
        std::string Description() override {
            return "Beautiful " + core->Description();
        }
    };
    
    class Shiny final : public IDecorator
    {
    public:
        Shiny(ICore* _core)
            : IDecorator{ _core }
        { }
    
    public:
        std::string Description() override {
            return "Shiny " + core->Description();
        }
    };
    
    
    int main()
    {
        ICore* core = new Core;
        ICore* decorated_core = new Beautiful{ new Shiny{ core } };
    
        core->Describe();
        decorated_core->Describe();
    
        delete decorated_core;
    
        return 0;
    }
    

    输出:

    I am Core
    I am beautiful shiny Core
    

    如您所见,这里的装饰器没有更改接口(类原型)- 没有向核心添加新功能。此外,它没有改变任何现有的功能。然而,它所做的是对现有行为的扩展。它确实装饰了 core 的描述2 个新词。请注意 - 此装饰发生在运行时。如果我们决定将装饰顺序从new Beautiful{new Shiny{core}} 更改为new Shiny{new Beautiful{core}},单词顺序也会更改(从beautiful shiny Core 更改为shiny beautiful Core)。


    但是,如果您真的-真的想实现您的主要意图 - 使用装饰器添加一个全新的功能......有一种方法可以让您模仿这种行为。它在 C++14 中看起来很丑,所以这里是 C++17 代码:

    class Core
    {
    public:
        void CoreFunctional() {
            std::cout << "Core functional." << std::endl;
        }
    };
    
    template<typename T>
    class Extend : public virtual T
    {
    public:
        Extend() = default;
        Extend(const T&) {  }
    
    public:
        void ExtendedFunctional() {
            std::cout << "Extended functional." << std::endl;
        }
    };
    
    template<typename T>
    class Utility : public virtual T
    {
    public:
        Utility() = default;
        Utility(const T&) {  }
    
    public:
        void UtilityFunctional() {
            std::cout << "Utility functional." << std::endl;
        }
    };
    
    
    int main()
    {
        Core core;
        core.CoreFunctional();
    
        auto decorated_core = Utility{Extend{core}};
        decorated_core.CoreFunctional();
        decorated_core.ExtendedFunctional();
        decorated_core.UtilityFunctional();
    }
    

    输出如你所料,但我不太确定,如果这可能被认为是一个装饰器......

    【讨论】:

    • 我喜欢 C++17 的例子。两个问题。 1.Extended{Utility{core}}是不同的对象吗? 2.如果我想要一个向量,我可以把Extended{Utility{core}}和Utility{Extended{core}}放在同一个向量中吗?
    • 1.是的,Extended{Utility{core}} 是一个不同的对象。 2. 我相信,只有std::vector&lt;Core&amp;&gt; 会起作用。 :(这就是为什么我称之为“模仿”
    【解决方案2】:

    装饰器设计的重点是对象可以具有不同的功能集。 ...似乎我需要为每个类都有一个“添加/删除”功能,这违背了这个设计的目的。

    没有。与几乎所有最知名的模式一样,装饰器模式都是关于接口的,因此(在 C++ 中)是虚拟成员函数。

    你定义你的基类(一个抽象的或一个你想用作基础的具体的),其中可以装饰的方法是虚拟的。
    装饰器 decores 存在的东西,它既不添加也不删除功能。
    每当您定义装饰器时,您最终都会覆盖这些方法以丰富它们并迭代地调用相同方法的基类实现。然后你将指针/引用传递给基类,用户不知道它们是否被装饰。只要调用它,正确的事情就会发生。

    让我们考虑一下。如果您添加一个新方法,您如何从引用或指向基类的指针中调用它?你不能,所以你需要实际的类型,即派生类型。
    这违背了设计的目的,而不是你必须向基类添加方法才能能够用派生的方式装饰它。

    如果您正在寻找一种允许您在类中添加或删除函数的模式,请考虑使用 mixins 或其他方式。这不是装饰器的目标。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-09
      • 2011-04-02
      • 2016-06-17
      • 2019-01-09
      • 2014-06-12
      • 2017-10-12
      • 1970-01-01
      • 2017-01-09
      相关资源
      最近更新 更多