一. 模板方法模式
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
应用场景:
- 当你希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可以使用模板方法模式
- 当多个类的算法除一些细微不同之外几乎完全一样时,你可使用该模式。但其后果就是,只要算法发生变化,你就可能需要修改所有的类
我们来模拟一个例子,制定工作计划
#include <cstdio>
class WorkPlan{
protected:
virtual void hook1(void) const{};
virtual void morning(void) const = 0;
virtual void hook2(void) const{};
virtual void afternoon(void) const = 0;
virtual void hook3(void) const{};
virtual void evening(void) const = 0;
virtual void hook4(void) const{};
public:
void show(void){
printf("WorkPlan: \n");
this->hook1();
this->morning();
this->hook2();
this->afternoon();
this->hook3();
this->evening();
this->hook4();
}
};
class MyWorkPlan : public WorkPlan{
private:
void hook1(void) const override{
printf("1. Morning run.\n");
}
void morning(void) const override{
printf("2. Go to class.\n");
}
void hook2(void) const override{
printf("3. Afternoon nap.\n");
}
void afternoon(void) const override{
printf("4. Go to class.\n");
}
void evening(void) const override{
printf("5. Play soccer.\n");
}
};
class YourWorkPlan : public WorkPlan{
private:
void morning(void) const override{
printf("1. Go to class.\n");
}
void hook2(void) const override{
printf("2. Afternoon nap.\n");
}
void afternoon(void) const override{
printf("3. Go to class.\n");
}
void evening(void) const override{
printf("4. Play basketball.\n");
}
void hook4(void) const override{
printf("5. Memorize words.\n");
}
};
int main(void){
MyWorkPlan my_plan;
YourWorkPlan your_plan;
my_plan.show();
printf("============================\n");
your_plan.show();
return 0;
}
模板方法基于继承机制:它允许你通过扩展子类中的部分内容来改变部分算法;策略模式基于组合机制:你可以通过对相应行为提供不同的策略来改变对象的部分行为。
模板方法在类层次上运作,因此它是静态的。策略模式在对象层次上运作,因此允许在运行时切换行为。
二. 外观模式
外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
应用场景:
- 如果你需要一个指向复杂子系统的直接接口,且该接口功能有限,则可以使用外观模式
- 如果需要将子系统组织为多层结构,可以使用外观模式。创建外观来定义子系统中各层次的入口。你可以要求子系统仅使用外观来进行交互,以减少子系统之间的耦合。 我们来模拟一个例子,大学生新生报到,新生报到流程有:报到登记、缴费、去宿舍…
#include <cstdio>
#include <string>
#include <memory>
class Register{
public:
void goRegister(const std::string &name){
printf("%s registers successfully.\n", name.c_str());
return;
}
};
class Pay{
public:
void pay(const std::string &name){
printf("%s pays successfully.\n", name.c_str());
}
};
class Dormitory{
public:
void goDormitory(const std::string &name){
printf("This is %s, everyone is welcome.\n", name.c_str());
}
};
class Facade{
private:
std::unique_ptr<Register> sub_register;
std::unique_ptr<Pay> sub_pay;
std::unique_ptr<Dormitory> sub_dormitory;
public:
Facade(){
this->sub_register = std::make_unique<Register>();
this->sub_pay = std::make_unique<Pay>();
this->sub_dormitory = std::make_unique<Dormitory>();
}
public:
void checkin(const std::string &name){
this->sub_register->goRegister(name);
this->sub_pay->pay(name);
this->sub_dormitory->goDormitory(name);
}
};
int main(void){
Facade facade;
facade.checkin("Alice");
return 0;
}
如果要充分发挥这一模式的优势,你必须确保所有客户端代码仅通过外观来与子系统进行交互。此后客户端代码将不会受到任何由子系统代码修改而造成的影响,比如子系统升级后,你只需修改外观中的代码即可。
三. 单一职责原则
单一职责原则是说,就一个类而言,应该仅有一个引起它变化的原因
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
软件设计真正要做的内容,就是发现职责并把这些职责相互分离
四. 依赖倒转原则
依赖倒转原则是说
- 高层模块不应该依赖低层模块;两者都应该依赖抽象。
- 抽象不应该依赖细节;细节应该依赖抽象。
例如主机电脑维护,我们只需要更换对应的零件就可以了,无论主板,CPU,内存,硬盘都是在针对接口设计的,如果针对实现设计,内存就要对应到具体的某个品牌的主板,那就会出现换内存也需要把主板换了的尴尬。
依赖倒转原则其实可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计了。
作业
数 x 称为函数 f 的不动点,如果 x 满足方程 f(x) = x。编写程序,以一个函数和初始猜测为参数,产生该函数的一个不动点的近似值。