Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ——Martin Fowler
如何设计软件?
Kent Beck给出了简单设计原则:
public void setUserName(final String userName) {
this.userName = userName;
}
public void changeUserName(final String userName) {
this.userName = userName;
}
@Getter
public class Customer {
private String name;
private Wallet wallet;
}
public class Wallet {
private float value;
public float getTotalMoney() {
return value;
}
public void setTotalMoney(float newValue) {
value = newValue;
}
public void addMoney(float deposit) {
value += deposit;
}
public void subtractMoney(float debit) {
value -= debit;
}
}
public class PaperBoy {
public void receive(Customer customer, float payment) {
// 这么付钱有什么问题?
Wallet wallet = customer.getWallet();
if (wallet.getTotalMoney() > payment) {
wallet.subtractMoney(payment);
} else {
// ...
}
}
}
public class Customer {
@Getter
private String name;
private Wallet wallet;
public float getPayment(float bill) {
if (wallet != null) {
if (wallet.getTotalMoney() > bill) {
wallet.subtractMoney(bill);
}
}
return wallet.getTotalMoney();
}
}
public class PaperBoy {
public void receive(Customer customer, float payment) {
float customerPayment = customer.getPayment(payment);
if (customerPayment == payment) {
// say thank you and give customer a receipt
} else {
// come back later and get my money
}
}
}
someClass.m1().m2().m3().m4();
而这样的函数链式调用(Fluent Interface)是不一样的
someStr.split().steam().map().reduce();
两者的区别是前者返回了别的对象(拿了顾客钱包),而后者依然是对象(Stream)本身
List<String> list = new ArrayList<>();
而不会使用
ArrayList<String> list = new ArrayList<>();
@Service
public void SomeService {
@Resource
private BaseService baseService;
public void someMethod() {
baseService.baseMethod();
}
}
struct TypeAPerson
: Child
, Underling
{
// 子女角色相关接口
void getAdviceFromParent() {...}
// 员工角色相关接口
void acceptTask() {...}
};
struct TypeBPerson
: Child
, Parent
, Boss
{
// 子女角色相关接口
void getAdviceFromParent() {...}
// 父母角色相关接口
void tellStory() {...}
// 老板角色相关接口
void assignTask() {...}
};
struct BaseTypePerson
: Child
{
// 子女角色相关接口
void getAdviceFromParent() {...}
};
struct TypeBPerson
: BaseTypePerson
, Parent
{
// 子女角色相关接口
void getAdviceFromParent() {...}
// 父母角色相关接口
void tellStory() {...}
};
但是面对只支持单继承的Java看来,上面的做法是行不通的(虽然多继承带来了对象复用的便利性,同时也引入了对象关系的复杂性)
实现继承会产生类爆炸的问题:
比如, 我们对一个基础类C附加行为a1,a2,a3,生成带有各种行为组合的类集合X;
采用继承的方式,结果集是 X = {Ca1,Ca2,Ca3,Ca1,a2,Ca1,a3,Ca2,a3,Ca1,a2,a3} ,足足有8个类;
这还只是添加三种行为,如果再加一种,就要维护16个类了
更可怕的是,使用实现继承将产生大量中间类,这些类没有任何价值,仅仅是因为语法不支持多继承用以传递父类的属性和行为
由此可见,单根继承的最大问题在于——只能解决单个变化方向的问题,对于多个变化方向无能为力
然后,面向组合编程登上舞台并大放异彩
再次需要明确的是:
类的作用,是为了模块化,我们应该遵从高内聚低耦合的原则去划分类,让软件容易应对变化,是我们无论采取何种方法论都应该遵从的原则;
而对象,是我们运行时承载了数据和行为的实体:它的种类和数量应该与领域的真实概念存在清晰、明确、直接的映射
我们将各个角色进行拆分,保证每个角色功能单一,通过角色的组合完成对象的构建
我们给一个Java版本的组合实现方案
public class TypeAPerson {
private Child child;
private Underling underling;
// 子女角色相关接口
void getAdviceFromParent() {
child.getAdviceFromParent();
}
// 员工角色相关接口
void acceptTask() {
underling.acceptTask();
}
}
class SomeRequest {
RequestType getType();
String getSomeA() {}
String getSomeB() {}
String getSomeC() {}
}
interface IHandler {
void handle(SomeRequest request);
}
class SomeClassA implements IHandler {
String handle(SomeRequest request) {
return request.getSomeA();
}
}
// 其他对象不一一列举
interface IRequest {}
interface SomeRequestA extends IRequest {
String getSomeA() {}
}
interface SomeRequestB extends IRequest {
String getSomeB() {}
}
interface SomeRequestC extends IRequest {
String getSomeC() {}
}
interface IHandler<T extends IRequest> {
void handle(T request);
}
class SomeClassA implements IHandler<SomeRequestA> {
String handle(SomeRequestA request) {
return request.getSomeA();
}
}
class Duck
def quack
# 鸭子叫
end
end
class FakeDuck
def quack
# 模拟鸭子叫
end
end
def make_quack(quackable)
quackable.quack
end
make_quack(Duck.new)
make_quack(FakeDuck.new)
软件工程是一个系统而有层次的科学,软件设计直接决定了代码质量与维护成本,软件质量建设是一个功不在当下的系统性工程,更要求我们从基础做起,形成团队共识甚至公约,为软件的规模化发展铺垫道路。
这篇小文,我们从面向对象最基本的三个特性出发探讨软件设计方法;当然,这只是软件设计方法论的冰山一角,我们抓住了几个非常底层的建设基点——OO理论的祖师爷Alan Kay对于面向对象的阐释,以及面向组合、面向接口等等,这些发展十数年依然有效作用在软件设计领域前沿的话题。