OO-Bootcamp
TWSchool-Courses
OO的一些关注点
- 可见性(Public)
- Equal vs Same
- ValueObject VS ReferObject
- Exception VS ErrorCode
- immutability
面向对象的S.O.L.I.D 原则
- Single Responsibility Principle (SRP) – 职责单一原则
- Open/Closed Principle (OCP) – 开闭原则
- Liskov substitution principle (LSP) – 里氏代换原则
- Interface Segregation Principle (ISP) – 接口隔离原则
- Dependency Inversion Principle (DIP) – 依赖倒置原则
其他编程或设计原则
- Don’t Repeat Yourself (DRY)
- Keep It Simple, Stupid (KISS)
- Composition over inheritance(喜欢组合而不是继承)
- Command-Query Separation (CQS) – 命令-查询分离原则
- You Aren’t Going to Need It (YAGNI) 只考虑和设计必须的功能,避免过度设计。
- Law of Demeter – 迪米特法则,最少知识原则
- Hollywood Principle – 好莱坞原则
- High Cohesion & Low/Loose coupling & – 高内聚, 低耦合
- Convention over Configuration(CoC)– 惯例优于配置原则
- Separation of Concerns (SoC) – 关注点分离
- Design by Contract (DbC) – 契约式设技
职责分配原则
基于职责设计对象(GRASP):General Responsibility Assignment Software Pattern
类的职责是类的契约和义务,包括行为职责(初始化其他对象、控制和协调其他对象的活动)和认知职责(对私有封装数据的认知、对其他对象的认知、对其能够导出或计算出的对象的认知)。GRASP是关于对象设计和职责分配的一组基本原则,有以下原则:
1. 创建者:如果以下条件之一(越多越好)为真时,将创建A的职责分配给B
B包含或组成聚集A
B记录A
B直接使用A
B具有A初始化所需要的数据,并且在创建A的时候会将这些数据传递给A
基本意图是寻找在任何情况下都与被创建者有直接连接的创建者。
2. 信息专家:把职责分配给具有实现这个职责所必需信息的信息专家(分配职责从清晰买描述对象职责开始,把职责分配给具有完成此职责所需信息的对象。“知其责,行其事”)。
3. 低耦合:分配职责,使耦合性尽可能低(低耦合是制定设计决策的时候必须牢记的原则)。
4. 控制器:把职责分配给能够代表以下选择之一的对象(控制器是在UI层首先接收和协调系统操作信息的第一个对象)。
代表整个系统、跟系统、运行软件的设备或主要的子系统。这些事外观控制器的所有变体。
代表用例场景,在该场景中发生的事件通常命名为<..>Handler/Session等。这些事用例或会话控制器。
5. 高内聚:分配职责要保持较高的内聚性(高内聚:元素具有高度的相关的职责,而且没有过多的职责)。
6. 多态:当相关选择或行为随类型(类)有所不同时,使用多态操作为变化的行为分配职责。
7. 纯虚构:对认为制造的一组类分配一组高内聚的职责,该类并不代表问题域的概念—虚构的事物,用以支持高内聚、低耦合和复用。
8. 间接性:将职责分配给中介对象,使其作为其他构件或服务之间的媒体,以避免他们之间的直接耦合。
9. 防止变异:识别或预计变化或不稳定之处,分配职责用以在这些变化之处创建稳定的接口。
封装
封装 (encapsulation) 隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。
- 封装途径
封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
- 封装的目的
是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。
思考封装 封装就是信息隐藏。 避免暴露知识点,例如用什么方式实现,就是知识点。
在这道题目里,“长度的换算,是通过计算比率实现的”,就是一个知识点,也即揭露了背后的工作原理,暴露了实现细节。 使用者应当只看到把一个长度转换成另外一个长度,而不应关心是用比率实现的,还是用其他方式来计算的。
封装:封装的意义,在于明确标识出会访问某个数据结构(用面向对象的术语来说就是 类成员变量)的所有接口。
有了封装,就可以明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者;而外部调用者也可以知道自己不可以碰哪里。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在;而不是外部调用者为了完成某个功能、却被碍手碍脚的private声明弄得火冒三丈;最终只能通过怪异、复杂甚至奇葩的机制,才能更改他必须关注的细节——而且这种访问往往被实现的如此复杂,以至于稍不注意就会酿成大祸)。
异常
- 异常本身的Nameing,有助于沟通
- 强制catch(java)
- 异常的集成和设计,Class,继承,可以使用到类的好处
- 异常可以向上抛,便于集中处理
- RuntimeException和非RuntimeException,语言层面支持多种异常处理方式,可以起到沟通的* 作用
- 对问题的反馈更清晰,便于代码的整洁
- 强制当前的中断流程,跳出当前的执行流程,直接逐级向上找到这个异常的处理程序,这个是* 错误码做不到的
- Http return code 是一个典型的错误码实现方式
重构
- 名词定义:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
- 动词定义:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
- 为何重构:使软件更容易理解,改进软件设计,帮助找Bug,提高编程速度
- 何时重构:三次法则:事不过三,三则重构
- 重构与设计:重构不需要设计?可以做到,但你仍然可以做预先设计,但是不必一定找出正确地解决方案,刺客的你只需要一个足够合理的解决方案就够了。在实现这个初始解决方案的时候,你对问题的理解也会加深,可能觉得方案与设想的有些不同。只要有重构这个利器,让日后的修改成本不再高昂。这种转变导致一个结果,软件设计向建华前进了一大步。
*
Bad Smell
- Duplicated Code(重复代码)
- Long Method(过长函数)
- Large Class(过大的类)
- Long Parameter List(过长参数列)
- Divergent Change(发散式变化)[一个类受多种变化的影响]
- Shotgun Surgery(散弹式变化)[一种变化引起多个类相应修改]
- Feature Envy(依恋情节)[一个类的方法里依赖了太多另一个类的数据]
- Data Clumps(数据泥团)[删除掉一个属性其他一些属性没有意义]
- Primitive Obsession(基本类型偏执)
- Switch Statements(switch 惊悚现身)[多处switch,多处修改]
- Parallel Inheritance Hierarchies(平行继承体系)
- Lazy Class(冗余的类?)
- Speculative Generality(夸夸其谈未来性)[删就一个字]
- Temporary Field(令人迷惑的临时变量)
- Message Chains(过度耦合的消息链)
- Middle Man(中间人)
- Inappropriate Intimacy
- Alternative Classes with Different Interfaces(异曲同工的类)
- Incomplete Library Class(不完美的库类)
- Data Class(纯数据类)
- Refused Bequest(被拒绝的遗赠)[继承转组合]
- Comments(过多的注释)
设计模式
- 针对的是业务问题上的相似性,不是实现方式上的相似性。
- 为什么用设计模式?什么是设计模式?解决特定问题的特定方案
思考:(策略 VS 继承)
- 通过继承的时候有可能当引入Manager的时候,因为FindLocer的逻辑与其他Robot的差别,会导致影响Findlocker的接口,产生味道,对象结构显得越来越别扭,促使转向策略模式
- 继承是静态结构,策略是动态组合模式,继承更加固定以及稳定,而策略则更加的灵活及不稳定。
- 类爆炸是由继承导向策略的一个味道
- 策略总是基于类型的判断,则是由策略转向继承的一个味道
- 如果子类不是已经存在可以直接使用的情况(每次都要new一个新的子类),需要考虑继承
- 参考《重构》《重构到模式》
- 如果组合的方式是有限且固定的,那使用组合反而不如使用继承的约束更强,表达的信息更明确,直接,简单。
- 如果子类复用了超类的行为(实现),却又不愿意支持超类的接口,Refused Bequest的坏味道就会变得浓烈。拒绝继承超类的实现,这一点我们不介意;但如果拒绝继承超类的接口,我们不以为然。不过即使你不愿意继承接口,也不要胡乱修改集成体系,应该运用“替换成代理”来达到目的
- 为判断自己到底应该选用组合还是继承,一个最简单的办法就是考虑是否需要从新类上溯造型回基础类。若必须上溯,就需要继承。但如果不需要上溯造型,就应提醒自己防止继承的滥用。但只要记住经常问自己“我真的需要上溯造型吗”,对于组合还是继承的选择就不应该是个太大的问题.
http://www.cnblogs.com/wangyingtao/archive/2008/10/26/1319759.html
开闭原则、优先使用组合,你还记得吗?
在我们很多OO程序员的脑子里总是存在这样一个观念:没有继承的程序不是OO的程序,看到重复总是想到继承。当初我也是这样想的,有的时候看到自己画的庞大的继承类图,心里在乐呵呵的笑。可继承总是不给面子,一个小小的变化就将这个看似稳定的体系弄的支离破碎。
还是回到我们的例子,在这个例子中变化的是各高校的报到步骤,本着发现变化、封装变化、隔离变化的原则我们将报到的步骤分离出来,独立成类。
在本篇我们从模板方法谈起,聊了一些模板方法随着项目的发展可能造成的问题,但这并不是模板方法的弊端,模板方法关注的是算法骨架的复用,如果你发觉新的问题出现,这可能就是模板方法不再适用的信号。通过我们对项目的扩展,发现继承在某些时候并不是都能达到代码复用的目的,这个时候我们应该考虑组合了,而且继承是一种静态的编译期的行为(针对像C#这种强类型静态语言而言),代码一经写定我们就没有选择的余地了。