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