桥接模式:如何定义调用派生类实现的函数

Posted

技术标签:

【中文标题】桥接模式:如何定义调用派生类实现的函数【英文标题】:Bridge pattern: how to define functions that call the implementation of the derived class 【发布时间】:2021-06-25 15:55:10 【问题描述】:

我正在研究一种聚合物模拟代码,该代码使用桥模式来分离抽象(模拟中涉及的对象)及其实现(它们是如何表示的,例如每个单体一个粒子、连续网格等)。我面临的一个问题是我正在努力定义抽象的类层次结构,其中较低的抽象可以执行在层次结构中更高的其他类上定义的函数。

我想定义一个基本抽象Monomer,它包含许多调用MonomerImpl 实现的方法。比如说,一种方法applyDisplacement 在空间中移动单体。但是,当我定义派生抽象MonomerTypeA 和几个实现MonomerTypeAImplGridMonomerTypeAImplParticles 等时,我想调用applyDisplacement,定义在层次类的顶部,但使用我的具体实现单体类。

这是我尝试过的一个例子:

class Monomer

  public:
    ...
    void applyDisplacement(Vector displacement)
    
       getImplementation()->applyDisplacement(displacement);
    

  private:
    std::unique_ptr<MonomerImpl> p_impl;
    virtual MonomerImpl* getImplementation();

;

class MonomerImpl

  public:
    ...
    void applyDisplacement(Vector displacement)
    
       for (int i=0; i<positions.size(); i++)
       
           positions[i] += displacement;
       
    

  private:
    std::vector<Vector> positions;

;

还有:

class MonomerTypeA : Monomer

  public:
    ...

  private:
    std::unique_ptr<MonomerTypeAImpl> p_impl;
    MonomerImpl* getImplementation()
    
        return (MonomerImpl*) p_impl.get();
    

;

class MonomerTypeAImpl : MonomerImpl

...

如果我使用虚函数getImplementation 函数只能返回一个MonomerImpl* 指针,那么我就无法实现任何附加功能。或者,如果我尝试放弃继承而只定义一堆不同的实现,我将有很多代码重复。

有没有两全其美的解决方案?

【问题讨论】:

如图所示,您没有任何派生类。您是否打算让MonomerTypeA 继承自Monomer 【参考方案1】:

我的答案将分为代码、代码中的一些 cmets 和此处的文本。

一、代码:

#include <iostream>
#include <memory>
#include <vector>

// Create a pure virtual interface for your MonomerImpls
class MonomerImpl 
 public:
  virtual ~MonomerImpl() = default;  // Required in non-concrete classes
  void applyDisplacement(int displacement) 
    std::cout << "From pure virtual interface " << displacement << ":\n";
    applyDisplacement_Helper(displacement);
  

  // Protected so inherited classes can access it freely
 protected:
  std::vector<int> positions;

 private:
  // Helper
  virtual void applyDisplacement_Helper([[maybe_unused]] int displacement) = 0;
;

// Public inheritance is what you want a majority of the time
class MonomerTypeAImpl : public MonomerImpl 
 public:
  MonomerTypeAImpl() = default;

 private:
  void applyDisplacement_Helper([[maybe_unused]] int displacement) override 
    std::cout << "Specialization From Monomer A Impl\n";
  
;

// Second MonomerImpl child to illlustrate
class MonomerTypeXImpl : public MonomerImpl 
 public:
  MonomerTypeXImpl() = default;

 private:
  void applyDisplacement_Helper([[maybe_unused]] int displacement) override 
    std::cout << "specialization From Monomer X Impl\nYowza\n";
  
;

// If you'll have many Monomers, it's better for the base to be abstract
// In cases where this similar operation is being done, NVI (non-virtual
// interface) can be quite helpful.
//
// I just have a Monomer class, and you can feed different objects different
// MonomerImpls to get the desired behavior
class Monomer 
 public:
  Monomer(MonomerImpl* impl) : p_impl(impl) 
  void applyDisplacement(int displacement) 
    p_impl->applyDisplacement(displacement);
  

 private:
  std::unique_ptr<MonomerImpl> p_impl;
  // I got rid of the get_impl() function, it added an extra step for no gain
;

int main() 
  MonomerImpl* monoImpl = new MonomerTypeAImpl();

  Monomer mono(monoImpl);
  mono.applyDisplacement(42);

  std::cout << "\n\n";

  Monomer xMono(new MonomerTypeXImpl());
  xMono.applyDisplacement(350);


输出:

From pure virtual interface:
From Monomer A Impl: 42


From pure virtual interface:
From Monomer X Impl: 350
Yowza

我不得不删除您的 Vector 类(未提供),所以我只是将其更改为 int 以便它可以编译。所做的更改并不大,因为大部分内容已经存在。如果您将有许多不同的实现,我的建议是为实现创建一个纯虚拟基类。它标准化接口并将代码放在它所属的具体(对象是可声明的)类中。就像我在评论中所说,如果许多派生类都做同样的事情,使用 NVI 或 Non-Virtual I接口可以帮助解决这个问题。 NVI 的一个例子是在MonomerImpl 类中。

(NVI 背后的想法是,基类可调用(公共)函数不是虚拟的。基类即使是抽象的,也可以做所有子类共有的事情,那么 调用一个受保护的/私有的虚拟函数,允许孩子们做他们的专门工作,并且只做他们专门的工作。虚拟函数被调用的顺序自然取决于共同的工作)

通过提供的代码,我完全不觉得从Monomer 派生得到任何东西。如果不同的 MonomerImpl*s 还不够,您可能有理由这样做。同样,我推荐一个抽象基类作为起点。

如果还有问题,我很乐意尝试回答。作为这个答案的参考,https://refactoring.guru/design-patterns/bridge

【讨论】:

【参考方案2】:

你有这个想法,但你的实现有点离岸。

如果我使用虚函数 getImplementation 函数只能返回一个MonomerImpl* 指针,那么我无法实现任何附加功能

更正:您将能够实现附加功能,但仅限于 MonomerImpl 类。并且虚函数也与继承一起工作,因此要使其工作,您必须从基类或抽象类继承。即接口。

问题是您没有制作胶水来连接Monomer 的各种实现。 Monomer 和它的实现类之间的这种连接可以使用继承或聚合来实现。但在这种情况下,聚合并不理想,因为 Monomer 与其实现之间存在 "is a" 关系。

例如

    #include <iostream>
    #include <memory>
    class vector
    
    ;
    
    // design an abstract base class .ie interface with pure virtual functions
    // of all the functions that an implementation must define .
    // All these functions will be callable from our base class with polymorphism
    class monomer_intf
    
      virtual auto
      do_apply_displacement(vector displacement) -> void
          = 0;
    ;
    
    // monomer is our concrete base class which will help use call our desired
    // implementations in the various derived class
    class monomer : public monomer_intf
    
      // defined because pure virtual functions need to be defined in the derived
      // class else the derived class also becomes an abstract class
      auto
      do_apply_displacement(vector displacement) -> void override
      
        // this dispatches to the dynamic class implementation
        // ie. the class monmer will point to or reference to
        do_apply_displacement(displacement);
      
    
    public:
      auto
      apply_displacement(vector displacement) -> void
      
        // calls the monomer class do_apply_displacement
        // which then dispatches to the implementation
        do_apply_displacement(displacement);
      
    ;
    
    // to get our implementation functionality only the virtual or overriden
    // function need to be implemented but we can have several functions as pleases
    // us
    class monomer_impl : public monomer
    
      auto
      do_apply_displacement(vector displacement) -> void override
      
        std::cout << "monomer called do_apply_displacement of monomer_impl \n";
      
    ;
    
    class monomer_type_a : public monomer
    
      auto
      do_apply_displacement(vector displacement) -> void override
      
        std::cout << "monomer called do_apply_displacement of monomer_type_a \n";
      
    ;
    class monomer_type_a_impl_grid : public monomer_type_a
    
      auto
      do_apply_displacement(vector displacement) -> void override
      
        std::cout << "monomer called do_apply_displacement of "
                     "monomer_type_a_impl_grid \n";
      
    ;

    class monomer_type_a_impl : public monomer_impl
    
    auto
       do_apply_displacement(vector displacement) -> void override
     
    std::cout
        << "monomer called do_apply_displacement of monomer_type_a_impl \n";
     
   ;

    
    // if we like this style . It simplifies the creation of the implementations 
    // and the execution of our need functions
    class monomer_abstraction
    
      std::unique_ptr<monomer> implementation_;
    
    public:
      explicit monomer_abstraction(monomer *inmplementation)
          : implementation_ inmplementation 
      
      
    
      auto
      apply_displacement(vector displacement)
      
        implementation_->apply_displacement(displacement);
      
    ;
    
    auto
    main() -> int
    
      // using stack allocated monomer_type_a_impl_grid
      monomer_type_a_impl_grid impl_grid;
      monomer &monomer_grid = impl_grid;
      monomer_grid.apply_displacement(vector);
    
      // using heap allocated monomer_impl
      auto impl_ = std::make_unique<monomer_impl>();
      monomer &monomer = *impl_;
      /// using virtual dispach apply_displacement will call the
      /// do_apply_displacement of it dynamic type
      monomer.apply_displacement(vector);
    
      // using monomer_abstraction as an abstraction to the implementations
      // of need functionalities
      auto abstraction
          = monomer_abstraction(std::make_unique<monomer_type_a_impl>().release());
      abstraction.apply_displacement(vector);
    

DRY 肯定是要走的路

【讨论】:

以上是关于桥接模式:如何定义调用派生类实现的函数的主要内容,如果未能解决你的问题,请参考以下文章

桥接模式(Bridge)

大话设计模式之桥接模式

桥接模式

18桥接模式

设计模式——桥接模式

设计模式--桥接模式