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
一个类只做一个活,并且尽量保持isolated,当别的类改变时候不应该影响到其他类的使用。
Circle的实现因为很多种原因而需要修改,因此没有很好的isolated。这时候吧Drawing分离出来就是一个好的解决方法。
Guideline: Prefer cohesive software entities. Everything that does not strictly belong together, should be separated.
2.The Open-Closed Principle
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
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.
- 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)
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)
我们需要inverse一下,把接口的所有转交给user,来解决这个问题
为MVC模式设计接口
- Guideline: Prefer to depend on abstractions (i.e. abstract classes or concepts) instead of concrete types.