类设计 – SOLID 设计原则

面向对象编程,类设计的好坏,对程序非常重要。类设计的合理,以后修改程序和扩展程序的功能有很大的帮助。遵守下面几个原则,有利于设计出合理的类。

1. 单一职责原则(Single Responsibility Principle)

每个类应该只有一个职责(A class should only have a Single responsibility),如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭到意想不到的破坏。

假设有一个报表类,现在有两个方法,一个显示报表,一个保存报表。

class Report
{
private:
     std::string report;

public:
      void show(const std::string &title);
      void save(const std::string &fileName);
};

这样设计就破坏了单一职责原则,无论显示方式的改变,还是保存方式的改变都需要修改这个类。因此分成分成单独的两个类更加合理,以后修改一起比较方便。

class Report
{
private:
    std::string report;

public:
    void show(const std::string &title);
};

class SaveReport
{
public:
      void save(const CReport& report, const std::string &fileName);
};

2. 开放-关闭原则(Open-Closed Principle)

实体应该对扩展开放,对修改关闭(Entities should be open for extension but closed for modification),开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,对于应用程序中的每个部分都刻意的进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

enum class Color { red, green, blue };
enum class Size { small, media, large };

struct Product
{
std::string name;
Color clr;
Size sz;
};

class ProductFilter
{
public:
    typedef std::vector<Product *> Items;

    Items byColor(const Items &items, const Color& color)
    {
        Items i;
        for(auto &item : items)
        {
             if(item->clr  == color)
                  i.push_back(item);
        }
        return i;
    }

    Items bySize(const Items& items, const Size& size)
    {
        Items i;
        for(auto &item : items)
        {
             if(item->sz == size)
                  i.push_back(item);
        }
        return i;
    }

    Items byColorAndSize(const Items& items, const Color& color, const Size& size)
    {
        Items i;
        for(auto &item : items)
        {
             if(item->clr == color && item->sz == size)
                  i.push_back(item);
        }
        return i;
    }
};

上面的例子违反了开放-关闭原则,不便于扩展,如果Product类增加了一个成员,ProductFilter的每个方法都有可能需要改动,换成下面的设计就好很多:

template<typename T> class ISpecification
{
public:
      virtual bool isSatified(const T* item) = 0;
};

template<typename T> class IFilter
{
public:
      virtual std::vector<T*> filter(const std::vector<T*>& items, const ISpecification<T*>& spec) = 0;
};

class BetterFilter : public IFilter<Product>
{
public:
     typedef std::vector<Product*> Items;
     virtual std::vector<Product*> filter(const std::vector<Product*>& items, const ISpecification<Product*>& spect) override
     {
          Items result;
          for(auto &item : items)
          {
               if(spec.isSatification(item))
                   result.push_back(item);
          }
          return result;
     }
}; 

class ColorSpecification : public ISpecification<Product>
{
     Color  color;
public:
     ColorSpecification(Color clr) : color(clr) {}

     virtual bool isSatified(const Product* item) override
     {
           return item->clr == color;
     }
};

class SizeSpecification : public ISpecification<Product>
{
     Size size;
public:
     SizeSpecification (Size sz) : size(sz) {}

     virtual bool isSatified(const Product* item) override
     {
           return item->sz == size;
     }
};

class ColorAndSizeSpecification : public ISpecification<Product>
{
     Color color;
     Size size;
public:
     ColorAndSizeSpecification (Color clr, Size sz) : color(clr), size(sz) {}

     virtual bool isSatified(const Product* item) override
     {
           return item->clr == color && item->sz == size;
     }
};

还可以设计的更加巧妙些:

template<typename T> AndSpecification : public ISpecification<T>
{
      ISpecification<T>& first;
      ISpecification<T>& second;
public:
      AndSpecification(ISpecification<T>& f, ISpecification<T>& s) : first(f), second(s) {}
      virtual bool isSatisfied(T* item) override
      {
            return first.isSatisfied(item) && second.isSatisfed(item);
      }
};

3. 里氏替换原则(Liskov Substitution Principle)

对象可以被它子类的实体替换,而不需要担心程序出错(Objects should be replaceable with instances of their subtypes without altering program correctness)

4. 接口分离原则(Interface Segregation Principle)

多个客户专用的接口比一个多用途接口更好(Many client-specific interfaces better than one general-purpose interface)

5. 依赖倒置原则(Dependency Inversion Principle)

抽象依赖而不是具体依赖(Dependencies should be abstract rather than concrete)

上层模块不应该依赖底层模块,但是两者都依赖抽象。(High-Level modules should not depend on low-level modules; Both should depend on abstractions。)

抽象不应该依赖细节,细节应该依赖抽象。换句话说,依赖接口和父类比依赖具体类更好(Abbstractions should not depend upon details. Details should depend upon abstractions.  In other words, dependencies on interfaces and supertypes is better than dependencies on concrete types。)

倒置控制(Inversion of Control (IoC)- the actual process of creating abstractions and getting them to replace dependencies。)