设计原则学习笔记

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)*********************************

  此原则描述的是类的接口如何设计的问题。
 
  强调类提供的接口应该是内聚的,即描述一类功能。 如果一个类的接口可以分为多组,提供了多个功能,最好
  分为多个基类实现。  这也和职能单一原则是一个意思。
 
  ★ 不应该强迫用户依赖于他们不使用的方法。

************************************************************************************

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值