这篇文章焦点并不在于继承以及多态性或者各种炫酷的设计模式上面,也不在与对未来的预测,而在于分析最基本的封装,提取类,组合
人遛狗的最简形式
public class People {
public static void main(String[] args) {
String name="小黑";
System.out.println("["+name+"]"+" 汪汪汪...");
}
}
如果问题本身就很简单,就不要搞复杂了
提取方法(Extract Method)
情况复杂一点,将name参数化,提取一个方法
public class People {
public void runDog(String name) {
System.out.println("["+name+"]"+" 汪汪汪...");
}
public static void main(String[] args) {
String[] names={
"小黑","小花","小黄"};
for(String name: names){
people.runDog(name);
}
}
}
只要存在围绕数据的一个处理方法,那么提取方法不错
提取类 (Extract Class)
可能狗变得更复杂了一些,于是出现了提取一个Dog类,如下面向对象版本的人遛狗,People类依赖Dog类(组合)
public class Dog {
private String name;
private Integer age
public Dog(String name,Integer age) {
this.name = name;
this.age=age;
}
public void bark() {
System.out.println("[name: "+this.name+" age: "+this.age+"] 汪汪汪...");
}
public void feed() {
System.out.println("[name: "+this.name+" age: "+this.age+"] 吃东西...");
}
}
public class People {
public void runDog() {
Dog dog = new Dog("小黑",2);
dog.bark();
}
public static void main(String[] args) {
People people = new People();
people.runDog();
}
}
如果以几个数据项为单元进行处理,那么这几个数据项和对应的处理方法提取为一个类不错
- 比如这里的bark和feed方法都需要使用name,age,如果用面向过程的代码,那么将需要给这两个方法都传递相同的变量,或者如果是引用传递,或者方法需要返回多个值,这样的话不如将这几项参数变成字段,从而多个方法可见,这些字段对类中的方法算全局变量
- 比如dog1和dog2进行比较,那不如提取一个类将equal方法放在这个类中
适度完美
只有需要的时候才使用额外的抽象,因为需要而使用,而不是为使用而使用,不用期望一步到位直接就设计出了完美的设计,设计本身就是反复迭代分析的结果,而且能拆就能建,能建就能拆,不用定位太高,首先代码能用就好,最原始的设计无论是纯面向过程也好,是蹩脚的面向对象也罢,之后都还能重构
不过可以不用,但是不能不知得失,所以下面将继续深入分析
## 提取方法和提取类比较
Extract Method | Extract Class |
---|---|
面向过程程序设计 | 面向对象程序设计 |
以直接容易的方式将大代码段分解成小代码段 | 更抽象化,高层的方式让代码更内聚,清晰 |
每个方法具有自己的局部变量 | 每个类具有实例方法共享的字段 |
简单的将数据以及一个数据相关的操作封装起来 | 将多项数据以及多个相关操作封装起来 |
封装抽象的机制是方法 | 封装抽象的机制是类 |
正向面向对象设计与分析
正常情况下,面向对象的程序设计的过程可能包含如下步骤
功能->用例图->问题分解->需求->领域分析->初步设计->实现->交付
这一系列过程,在领域分析的过程中可能用文本分析的方式,名词可能是候选类,动词可能是类的方法,以这样正向的方式来设计出面向对象的程序
面向抽象编程
高层 | 低层 |
---|---|
比如人遛狗,这3个字在一个高层上面,高层不关心低层,高层只有人遛狗的逻辑 | 至狗怎么被溜,只需要低层实现了那样的接口(有被溜的方法可以调用)就行 |
这种分层,抽象的思想在正向分析里面非常有用,这里重点不是面向接口之后,子类利用多态性达到灵活可变的能力,而在于分层,能够在更高的层次上编码,不用干什么都需要接触底层细节
实际上如果按照这种面向抽象的正向分析建模,那么代码里面的数据和数据对应的操作总是相距不远,有时候不得不叹服自然语言和机器语言的不谋而合,相反直接按照机器语言即面向过程的思想编码,在可能一叶障目,在细节上纠结,陷入泥潭
###封装变化
有一种说法是将容易变化的部分封装起来,也就是封装变化
Extract Method | Extract Class |
---|---|
当然进行这种重构之后,如果某个方法的算法变了,修改一个小方法总比修改一大段方法容易,但提取方法更加总要的是作用让当下代码更加清晰,容易理解 ,而不是重用或者应对变化 | 不可否则它可以将容易变化的部分封装到一个类里面,让未来更加容易只修改一个类中的代码而不用同时修改多个类,但未来不是重点,我想要的是当下如何设计让程序更加清晰,容易编写,不对未来做过多假设 |
结论:我不反对封装变化(如果第二次出现同样的变化,那就不要被同一颗子弹击中两次,更加准确的意思是第一次不对未来做过多假设),我更在乎现在,于是我的问题更加准确应该是探讨如何以当下为目标进行逆向面向对象设计与分析
如果狗只有一个字段
形式一:
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public void bark() {
System.out.println("["+this.name+"] 汪汪汪...");
}
}
public class People {
public void runDog() {
Dog dog = new Dog("小黑");
dog.bark();
}
public static void main(String[] args) {
People people = new People();
people.runDog();
}
}
形式二:
public class People {
public void runDog(String name) {
System.out.println("["+name+"] 汪汪汪...");
}
public static void main(String[] args) {
People people = new People();
people.runDog("小黑");
}
}
可以比较上面两种形式的代码,发现封装一个字段并没带来很明显好处
结论:一个类只封装一个字段会丧失对数据个数减少的优点,甚至额外多了一层类,反而复杂了,不过还是具有使类中字段对类的实例方法公用的价值
如果狗有两个字段
public class Dog {
private String name;
private int age;
public