JavaCoreTech-04-类设计技巧


类设计技巧

参考: Java核心技术 卷I 第4章 4.10
在学习书籍的基础上,结合作者自身在多个项目中的实际经验、加入了一些自己的理解和工程化的实际经验总结。
面向对象的三大特征:封装、继承、多态
本章介绍了 Java 这种面向对象语言的有关对象和类的基础知识。
为了真正做到面向对象,程序设计语言还必须支持继承和多态。
Java 提供了对这些特性的支持,具体内容将在下一章中介绍。

Java核心技术一书中简单地介绍了几点类设计的技巧。应用这些技巧可以使你设计的类更能得到专业OOP圈子的认可。
(注:OOP 是指 Object-Oriented Programming,即 面向对象编程)。

1. 一定要保证数据私有

这是最重要的; 绝对不要破坏封装性。

有时候, 需要编写一个访问器方法或更改器方法, 但是最好还是保持实例域的私有性。

很多惨痛的经验告诉我们, 数据的表示形式很可能会改变, 但它们的使用方式却不会经常发生变化。
当数据保持私有时, 它们的表示形式的变化不会对类的使用者产生影响, 即使出现 bug 也易于检测。

实际经验:
类设计:字段要隐藏。
因为会有检验或者触发逻辑,比如检验邮箱是否符合正则表达式,检验id是否唯一等等。
触发逻辑比如凡是订单状态变化就要发消息通知等
如果外界直接改字段值,可能就会绕过校验或者必要的触发逻辑,从而导致业务逻辑错误或非法数据、甚至导致安全问题。

2. 一定要对数据初始化

Java 不对局部变量进行初始化, 但是会对对象的实例域进行初始化。
最好不要依赖于系 统的默认值, 而是应该显式地初始化所有的数据, 具体的初始化方式可以是提供默认值, 也可以是在所有构造器中设置默认值。

3. 不要在类中使用过多的基本类型

就是说, 用其他的类代替多个相关的基本类型的使用。这样会使类更加易于理解且易于 修改。例如, 用一个称为 Address 的新的类替换一个 Customer 类中以下的实例域:
private String street; private String city; private String state; private int zip;
这样, 可以很容易处理地址的变化, 例如, 需要增加对国际地址的处理。

4. 不是所有的域都需要独立的域访问器和域更改器

或许, 需要获得或设置雇员的薪金。 而一旦构造了雇员对象, 就应该禁止更改雇用日
期, 并且在对象中, 常常包含一些不希望别人获得或设置的实例域, 例如, 在 Address 类中, 存放州缩写的数组。

5. 将职责过多的类进行分解

这样说似乎有点含糊不清, 究竟多少算是“ 过多” ? 每个人的看法不同。
但是, 如果明 显地可以将一个复杂的类分解成两个更为简单的类, 就应该将其分解
(但另一方面, 也不要走极端。 设计 10 个类, 每个类只有一个方法, 显然有些矫枉过正了)。

下面是一个反面的设计示例。

public class CardDeck // bad design 
{
   private int[] value;
   private int[] suit;
   public CardDeck() { . . . }
   public void shuffle0 {•••}
   public int getTopValueO { . . . }
   public int getTopSuitO { . . . }
}

实际上, 这个类实现了两个独立的概念: 一副牌(含有 shuffle 方法和 draw 方法)和一 张牌(含有查看面值和花色的方法)。另外,引入一个表示单张牌的 Card类。现在有两个类, 每个类完成自己的职责:

public class CardDeck
{
   private Card[] cards;
   public CardDeckO {. . } public void shuffle() { . . . } public Card getTopO { . . . } public void draw() { . . . }
}

public class Card {
   private int value; private int suit;

   public Card(int aValue, int aSuit) { . . . } public int getValueO { . . . }
   public int getSuitO { . . . }
}

6. 类名和方法名要能够体现它们的职责

与变量应该有一个能够反映其含义的名字一样, 类也应该如此(在标准类库中, 也存在 着一些含义不明确的例子, 如: Date 类实际上是一个用于描述时间的类 )。
命名类名的良好习惯是采用一个名词(Order)、 前面有形容词修饰的名词( RushOrder) 或动名词(有“ -ing”后缀)修饰名词(例如,BillingAddress)。
对于方法来说,习惯是访问 器方法用小写 get 开头 ( getSalary ), 更改器方法用小写的 set 开头(setSalary )

7. 优先使用不可变的类 —

LocalDate 类以及 java.time 包中的其他类是不可变的 没有方法能修改对象的状态。
类似 plusDays 的方法并不是更改对象, 而是返回状态已修改的新对象。

更改对象的问题在于, 如果多个线程试图同时更新一个对象, 就会发生并发更改。
其结果是不可预料的。 如果类是不可变的, 就可以安全地在多个线程间共享其对象。

因此, 要尽可能让类是不可变的, 这是一个很好的想法。
对于表示值的类, 如一个字符串或一个时间点, 这尤其容易。 计算会生成新值, 而不是更新原来的值。

当然, 并不是所有类都应当是不可变的。
如果员工加薪时让 raiseSalary 方法返回一个新 的 Employee 对象, 这会很奇怪。


文章作者: LuckLiu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuckLiu !
  目录