1. 软件开发中的视角:
a 概念:软件要做什么,负责什么
b 规约:软件的接口
c 实现:软件如何实现,如何履行负责的事情
这也是抽象一个类时要做的事情: 一个类要负责什么,怎么接口,怎么实现。
2.封装。
封装是对对象内数据直接访问的隐藏。
------- 这样理解比较片面。封装不仅隐藏数据,也隐藏了职责的具体实现。使调用者不知道实现过程,则修改实现时对调用者的影响也小。
同时,通过多态,对调用者也隐藏了对象类型。
通过“自己负责自己的职责”,不同类型对象之间的影响减小。一个类型中方法的修改仅影响本类型对象,不影响其他的。
封装使对象内部行为变化对其他对象变的不可见。
封装的是职责、知识。 比如一个矩形,他对外提供了长、宽。 外部程序如果读取他的长宽并计算面积,就是知识的泄漏。面积应该由矩形自己提供,这应该是他的职责,他有这个知识。
3.对象: 对象是由其责任定义的。即抽象出对象,这个对象完成哪些功能。
抽象类: 定义概念上相似的一组类的方法和公共属性。 抽象类不能实例化。
类: 对象的蓝图,为其类型的对象定义方法和数据。
4.重载: 函数名相同,入参类型、个数、顺序不同。 编译器根据调用时入参的情况,选择执行哪个函数。
5.构造函数和析构函数。 构造函数的重载。
6.new delete 与molloc和free的区别:
调用malloc、free不会触发构造函数和析构函数 ,调用new和delete可以。这两个是c++的语言。
★ 组合:持有另一个对象的引用。可以保持引用的多态性,又比类继承灵活。 组合的一种特殊用法:委托,即把请求交给另一个类对象去处理,自身维护被委托者的引用。 state模式是典型的委托。
★ 聚合:聚合意味着一个对象拥有另一个对象、或对另一个对象负责。 意味着被聚合对象和聚合者有相同的生命周期。
★ 关联:一种弱联系。类的对象或引用以函数参数等形式交互。
==================================== 原则 ==========================================================
************* 单一职责原则 (SRP) ****************
此原则强调的是内聚性,即类的职责内聚。
职责可以定义为“变化的原因”。如果有多个原因会导致去变化一个类,那么这个类的职责就不单一。那么就会产生耦合性。
例如甲乙都使用一个类,甲关注职责1,乙关注职责2,当由甲的需求变化导致类的变化,可能会影响到乙的使用。
***************************************************
************* 开放封闭原则(OCP) *****************
对扩展开放。模块可扩展,可以改变模块的功能。
对更改封闭。模块扩展时,可以不改变原来已经写好的代码做到。
思路:通过抽象,使模块依赖于一个固定的抽象体,可以通过派生此抽象体做到扩展,同时又对此模块的更改封闭。
开闭的主要机制是抽象和多态。
例如:shape程序
struct circle{
BYTE shapetype;
double radius;
point center;
};
struct square{
BYTE shapetype;
double side;
point topleft;
};
void DrawShape(shape s)
{
switch(s->shapetype)
{
case square:
DrawSquare((square*)s);
break;
case circle:
DrawCircle((circle*)s);
break;
}
}
这个程序对扩展就无法封闭。比如增加三角形。除了实现DrawTriangle(),还要修改主程序DrawShape。
假如主程序还有其他函数MoveShape、changeShape。。。。,则需要修改主程序的很多地方。
假如有个函数里面对square、circle处理相同,并没使用switch,则找出这个修改点都是非常困难的。
★ 封闭是建立在抽象的基础上的。
例如做这样抽象:
class shape{
virtual void Draw() = 0;
}
class circle : public shape{
virtual void Draw();
}
class square : public shape{
virtual void Draw();
}
void DrawShape(shape s)
{
s->Draw();
}
★ 编码思路转变的关键是: 不再关心事情是怎么完成的,是谁去完成的。而是仅关注到这个事情应该由这么一个角色去完成,至于哪个角色、怎么完成的,就是具体类的实现了。
比如:部长说:请相关问题科长解决问题。--------- 这句话是主程序,不再关心科长是谁,也不关心问题是怎么解决的。代码很可能是这样的:
{
科长 = getkezhangByproblem(问题);
科长->sloveproblem(问题);
}
其中getkezhangByproblem()是需要维护科长变化的。 sloveproblem则是各科室具体类的解决方法。
这样不管再增加什么形状,主程序DrawShape都不需要变化,仅增加Triangle的类成员函数即可。
但是,这种抽象只能隔离类型增加的变化,不能隔离排序的变化。比如要求先画圆,再画方块。这就要求再增加一个抽象,排序。
void DrawAllShape(shapeList s)
{
s->sort();/* 新增抽象,对shape对象列表排序。然后再画出。sort函数调用每个shape的prio函数进行判断优先级*/
★注意!问题出现了!这个函数对形状增加又不是封闭的了! 新增形状必然要修改每个shape子类的prio函数。
for()
{
s.i->Draw();
}
}
所以没有绝对的封闭可以应对所有的变化,只有先猜到或已知要发生的变化,才能构造对应的抽象来隔离变化。
心得:公共流程、主题函数如果多用抽象的函数,则应对起变化来改动很小。
但是过多的抽象也会给程序维护带来负担。
***********************************************************************************************************
************************** 替换原则(LSP)******************************************
此原则描述的是类的继承时的问题。但他帮助我更好的理解 封装的含义。
子类型必须能替换掉它们的基类型。
替换原则是开闭原则的基础。只有满足了替换原则,抽象-多态的方式才能实现扩展。
最好是基类定义好所有的方法,子类仅通过不同的方式来实现这些方法,但是不新增方法。
例如:
class rectangle{
virtual void setwidth(int x){ w =x};
virtual void setlong(int x){ l = x};
}
class square:public rectangle{
virtual void setwidth(int x){ w = l = x;};
virtual void setlong(int x){ w=l=x};
}
生活中一个正方形一定是一个矩形,所以由矩形派生出正方形类。 square重载设置边长的函数。
但这个例子就违反了替换原则。比如在如下函数中:
void fun(rectangle * r)
{
r->setwidth(4);
r->setlong(5);
assert(r.Area(20)); /* 当用一个square类型的对象传入fun时,assert就会出错! */
}
★ 上面例子再次说明: 类封装的是行为。是否可替换也是从行为角度出发的。
★ 行为方式才是软件真正关注的问题。
************************************************************************************
******************************** 依赖倒置原则(DIP)********************************
此原则描述的是模块上下层之间、有接口调用时的场景。
a高层模块不应该依赖于低层模块。 二者都应该依赖于抽象
b抽象不应该依赖于细节。细节应该依赖于抽象。
高层模块包含重要的策略选择和业务模型。应尽量使这部分不动,使低层的模块依赖他们。
简单的换个说法: 接口的所有权归谁? 归接口的使用者,而不是接口的提供者。
用c++的写法,则要求高层策略和低层实现分离。抽象和具体实现分离。任何变量都不持有一个指向具体类的指针、任何类都不应该从具体类派生、任何方法都不应该覆写他基类中已经实现的方法。
★ 其实就是向稳定的方向依赖。 哪种方向是稳定的呢 —— 抽象。
还有一种做法是接口没有所有者,接口是独立通用的。这样,接口的多个继承都可以被调用者使用;同时,只要懂得如何使用
此接口的模块,都可以作为调用者。即多增加一个抽象环节,将调用层和实现层模块分开了。
★ 例子: 一个bottom控制light的模型背后,抽象是什么? 按钮控制灯泡的背后抽象是 检测用户输入对象 发控制信号给 电器对象,可以是灯泡,也可以是其他。
所以模型应该是 bottom 依赖于 控制器, 灯泡继承自控制器。
************************************************************************************
******************************* 接口隔离原则(ISP)*********************************
此原则描述的是类的接口如何设计的问题。
强调类提供的接口应该是内聚的,即描述一类功能。 如果一个类的接口可以分为多组,提供了多个功能,最好
分为多个基类实现。 这也和职能单一原则是一个意思。
★ 不应该强迫用户依赖于他们不使用的方法。
************************************************************************************