Core C++笔记--面对对象编程的SOLID原则

Klaus Iglberger, The SOLID Principles at Core C++ meetup 观后感。Klaus Iglberger每次的分享都能收获不少,所以这次拿好纸笔好好学习。

What SOLID stands for?

  • Single-responsibility principle
  • Open-closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

1.Single-responsibility principle

Core C++笔记--面对对象编程的SOLID原则

一个类只做一个活,并且尽量保持isolated,当别的类改变时候不应该影响到其他类的使用。

Core C++笔记--面对对象编程的SOLID原则

Circle的实现因为很多种原因而需要修改,因此没有很好的isolated。这时候吧Drawing分离出来就是一个好的解决方法。

Core C++笔记--面对对象编程的SOLID原则

Guideline: Prefer cohesive software entities. Everything that does not strictly belong together, should be separated.

2.The Open-Closed Principle

Core C++笔记--面对对象编程的SOLID原则

OCP: A Procedural Approach


enum ShapeType
{
    circle,
    square
};

class Shape
{
public:
    explicit Shape( ShapeType t )
            : type{ t }
    {}
    virtual ~Shape() = default;
    ShapeType getType() const noexcept;
private:
    ShapeType type;
};

class Circle : public Shape
{
public:
    explicit Circle( double rad )
            : Shape{ circle }
            , radius{ rad }
            , // ... Remaining data members
    {}
    virtual ~Circle() = default;
    double getRadius() const noexcept;
    // ... getCenter(), getRotation(), ...
private:
    double radius;
    // ... Remaining data members
};
void translate( Circle&, Vector3D const& );
void rotate( Circle&, Quaternion const& );
void draw( Circle const& );

class Square : public Shape
{
public:
    explicit Square( double s )
            : Shape{ square }
            , side{ s }
            , // ... Remaining data members
    {}
    virtual ~Square() = default;
    double getSide() const noexcept;
    // ... getCenter(), getRotation(), ...
private:
    double side;
    // ... Remaining data members
};
void translate( Square&, Vector3D const& );
void rotate( Square&, Quaternion const& );
void draw( Square const& );

void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
    for( auto const& s : shapes )
    {
        switch ( s->getType() )
        {
            case circle:
                draw( *static_cast<Circle const*>( s.get() ) );
                break;
            case square:
                draw( *static_cast<Square const*>( s.get() ) );
                break;
        }
    }
}

int main()
{
    using Shapes = std::vector<std::unique_ptr<Shape>>;
    // Creating some shapes
    Shapes shapes;
    shapes.push_back( std::make_unique<Circle>( 2.0 ) );
    shapes.push_back( std::make_unique<Square>( 1.5 ) );
    shapes.push_back( std::make_unique<Circle>( 4.2 ) );
    // Drawing all shapes
    draw( shapes );
}

如果需要扩展,比如加入一个rectangle类,只需要在enum加上rectangle,并完成rectangle实现(还要加上新的switch case)

enum ShapeType
{
      circle,
      square,
      rectangle
};

OCP: An Object-Oriented Approach

经典的面对对象设计。完全抽象Shape类,使其成为一个interface,这样上面很烦人的switch就可以不需要了,直接使用override的draw方法。

class Shape
{
public:
    Shape() = default;
    virtual ~Shape() = default;

    virtual void translate( Vector3D const& ) = 0;
    virtual void rotate( Quaternion const& ) = 0;
    virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
    explicit Circle( double rad )
            : radius{ rad }
            , // ... Remaining data members
    {}
    virtual ~Circle() = default;
    double getRadius() const noexcept;
    // ... getCenter(), getRotation(), ...
    void translate( Vector3D const& ) override;
    void rotate( Quaternion const& ) override;
    void draw() const override;
private:
    double radius;
    // ... Remaining data members
};

class Square : public Shape
{
public:
    explicit Square( double s )
            : side{ s }
            , // ... Remaining data members
    {}
    virtual ~Square() = default;
    double getSide() const noexcept;
    // ... getCenter(), getRotation(), ...
    void translate( Vector3D const& ) override;
    void rotate( Quaternion const& ) override;
    void draw() const override;
private:
    double side;
    // ... Remaining data members
};

void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
    for( auto const& s : shapes )
    {
        s->draw();
    }
}

int main()
{
    using Shapes = std::vector<std::unique_ptr<Shape>>;
    // Creating some shapes
    Shapes shapes;
    shapes.push_back( std::make_unique<Circle>( 2.0 ) );
    shapes.push_back( std::make_unique<Square>( 1.5 ) );
    shapes.push_back( std::make_unique<Circle>( 4.2 ) );
    // Drawing all shapes
    draw( shapes );
}

但问题又来了,这样的设计违法了Single-responsibility的原则,我们把draw放到了类里面,加大了未来维护的难度。

OCP: A Type-Erased Approach

Shape类的构造函数能实现类似多态的方法

template< typename T >
    Shape( T const& x )
            : pimpl{ new Model<T>( x ) }
    {}
class Circle
{
public:
    explicit Circle( double rad )
            : radius{ rad }
            , // ... Remaining data members
    {}
    double getRadius() const noexcept;
    // ... getCenter(), getRotation(), ...
private:
    double radius;
    // ... Remaining data members
};
// 需要实现
void translate( Circle const&, Vector3D const& );
void rotate( Circle const&, Quaternion const& );
void draw( Circle const& );

class Square
{
public:
    explicit Square( double s )
            : side{ s }
            , // ... Remaining data members
    {}
    double getSide() const noexcept;
    // ... getCenter(), getRotation(), ...
private:
    double side;
    // ... Remaining data members
};

void translate( Square const&, Vector3D const& );
void rotate( Square const&, Quaternion const& );
void draw( Square const& );

// 此时的Shape不再是Circle的基类,而更像一个container
class Shape
{
private:
    struct Concept
    {
        virtual ~Concept() {}
        virtual void do_translate( Vector3D const& v ) const = 0;
        virtual void do_rotate( Quaternion const& q ) const = 0;
        virtual void do_draw() const = 0;
        // ...
    };
    
// 使用Circle时,Circle被包含在内部类Model的T object。
    template< typename T >
    struct Model : Concept
    {
        Model( T const& value )
                : object{ value }
        {}
        void do_translate( Vector3D const& v ) const override
        {
            translate( object, v );
        }

        void do_rotate( Quaternion const& q ) const override
        {
            rotate( object, q );
        }
        void do_draw() const override
        {
// 调用draw(T)
            draw( object );
        }
        // ...
        T object;
    };
// 类内定义友元实际是独立的函数
    std::unique_ptr<Concept> pimpl;
    friend void translate( Shape& shape, Vector3D const& v )
    {
        shape.pimpl->do_translate( v );
    }

    friend void rotate( Shape& shape, Quaternion const& q )
    {
        shape.pimpl->do_rotate( q );
    }

    friend void draw( Shape const& shape )
    {
        shape.pimpl->do_draw();
    }

public:
    template< typename T >
    Shape( T const& x )
            : pimpl{ new Model<T>( x ) }
    {}
    // Special member functions
    Shape( Shape const& s );
    Shape( Shape&& s );
    Shape& operator=( Shape const& s );
    Shape& operator=( Shape&& s );
    // ...
};
void draw( std::vector<Shape> const& shapes )
{
    for( auto const& shape : shapes )
    {
        draw( shape );
    }
}

int main()
{
    using Shapes = std::vector<Shape>;
    // Creating some shapes
    Shapes shapes;
    shapes.push_back( Circle{ 2.0 } );
    shapes.push_back( Square{ 1.5 } );
    shapes.push_back( Circle{ 4.2 } );
    // Drawing all shapes
    draw( shapes );
}

3.Liskov substitution principle

Core C++笔记--面对对象编程的SOLID原则

Core C++笔记--面对对象编程的SOLID原则

Option B虽然好看,但是不符合LSP。 在Option B中,父类多了更多的限制(有height又有weight)。虽然我们可以把height和weight设为同个值,但这会带了不少麻烦,比如空间的浪费,比如如果父类有setWidth的方法,难道Square类只改width不改height?

继承是一个is-a的关系,Square类应该是(is a)Rectangle类, 如果在父类Rectangle里就预设好了width, height(不符合Square类需求,Square只需要一个),会破坏这个is a的关系。 因此在例子里需要简化父类。

An overridden method of a subclass needs to accept the same input parameter values as the method of the superclass. That means you can implement less restrictive validation rules, but you are not allowed to enforce stricter ones in your subclass. 

Core C++笔记--面对对象编程的SOLID原则

  • Guideline: Make sure that inheritance is about behavior, not about data.
  • Guideline: Make sure that the contract of base types is adhered to.
  • Guideline: Make sure to adhere to the required concept.

4.Interface segregation principle

接口分离原则, 客户类应该可以通过不同的抽象基类来使用接口,因此不被强迫依赖那些它们不需要的接口(接口污染)。

”Clients should not be forced to depend on methods that they do not use.”
(Robert C. Martin, Agile Software Development)

”Many client specific interfaces are better than one general-purpose interface.”
(Wikipedia)

Core C++笔记--面对对象编程的SOLID原则

class Circle;
class Square;
class DrawStrategy
{
public:
    virtual ~DrawStrategy() {}
    virtual void draw( const Circle& circle ) const = 0;
    virtual void draw( const Square& square ) const = 0;
};

class Shape
{
public:
    Shape() = default;
    virtual ~Shape() = default;

    virtual void translate( Vector3D const& ) = 0;
    virtual void rotate( Quaternion const& ) = 0;
    virtual void draw() const = 0;
};


class Circle : public Shape
{
public:
    explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds )
            : radius{ rad }
            , // ... Remaining data members
            , drawing{ std::move(ds) }
    {}
    virtual ~Circle() = default;
    double getRadius() const noexcept;
    // ... getCenter(), getRotation(), ...
    void translate( Vector3D const& ) override;
    void rotate( Quaternion const& ) override;
    void draw() const override;
private:
    double radius;
    // ... Remaining data members
    std:unique_ptr<DrawStrategy> drawing;
};

class Square : public Shape
{
public:
    explicit Square( double s, std::unique_ptr<DrawStrategy> ds )
            : side{ s }
            , // ... Remaining data members
            , drawing{ std::move(ds) }
    {}
    virtual ~Square() = default;
    double getSide() const noexcept;
    // ... getCenter(), getRotation(), ...
    void translate( Vector3D const& ) override;
    void rotate( Quaternion const& ) override;
    void draw() const override;
private:
    double side;
    // ... Remaining data members
    std::unique_ptr<DrawStrategy> drawing;
};

把DrawStrtegy分离,接口变得不那么容易肿大了

class DrawCircleStrategy
{
public:
    virtual ~DrawCircleStrategy() {}
    virtual void draw( const Circle& circle ) const = 0;
};

class DrawSquareStrategy
{
public:
    virtual ~DrawSquareStrategy() {}
    virtual void draw( const Square& square ) const = 0;
};

// 当然还要Circle的DrawingStrategy改为std:unique_ptr<DrawCircleStrategy> drawing; 

这篇博文有更好的例子
https://www.cnblogs.com/gaochundong/p/interface_segregation_principle.html

  • Guideline: Make sure interfaces don’t induce unnecessary dependencies.

5.The Dependency Inversion Principle

”The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.”
(Robert C. Martin, Clean Architecture)

”a. High-level modules should not depend on low-level modules. Both should depend on abstractions.
 b. Abstractions should not depend on details. Details should depend on abstractions.”
(Robert C. Martin, Agile Software Development)

permission接口类在底层时候还是没法很好把High-level和low-level的模块分离(permission(I)还是依赖于Authorizer)
Core C++笔记--面对对象编程的SOLID原则
我们需要inverse一下,把接口的所有转交给user,来解决这个问题
Core C++笔记--面对对象编程的SOLID原则

为MVC模式设计接口
Core C++笔记--面对对象编程的SOLID原则

  • Guideline: Prefer to depend on abstractions (i.e. abstract classes or concepts) instead of concrete types.
上一篇:Java为游戏绘制地图网格


下一篇:【python+Tensorflow】socket通信实战(全网最全面实现介绍!!)