「设计原则二」
一、单一职责原则
单一功能职责,一个类应该有且仅有一个引起它变化的原因,也即一个类只负责一项职责。不仅仅是类,实际开发过程中,方法的的处理也是遵循尽可能处理单一的功能,否则应该考虑将它们进行拆分。
- 如果在设计时不遵循单一职责原则,一个类或者方法承载了多个功能,那么某一个职责变化时,有可能影响其他职责的变化,导致系统的不可控。
- 破坏单一职责原则,系统可读性、可维护性都会降低;造成大量代码冗余。
1.作用
- 降低了类或方法的复杂度。(由于粒度小,派生的类难免会增加,但同保持系统的稳定比,这点牺牲是值得的)。
- 职责清晰,提高了系统的可读性、可维护性。
- 对系统变更的适应能力更强,单一的职责修改对其他的职责影响很小。
2.实际问题
设计一个邮件接收系统,实际生活中,我们可以一个邮箱绑定多个其他厂商的邮箱账号,统一管理。
- 无非就是连接对应的邮箱提供厂商的系统,拉取邮件到客户端,自然地能想到如下接口设计:
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public interface EmailProvider {
void makeClient();
void downloadEmails();
void disConnectFromService();
}
//定义了三个方法,连接服务器,拉取最新邮件,断开连接;很明显的违背了单一职责原则。
//在这个接口中我们只想它的操作仅仅执行下载任务,连接的协议是可变的。可以是QQ邮箱、Gmail等
- 修改后的接口
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public interface EmailProvider {
void downloadEmails();
}
- 提供
Client
连接类管理连接,支持各种连接协议
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public class EmailClient {
private final List<EmailProvider> providers = new CopyOnWriteArrayList<>();
//EmailProvider 接口作为传参,面向接口
public void addProvider(EmailProvider provider) {
providers.add(provider);
}
public void downloadEmails() {
for (var provider : providers) provider.downloadEmails();
}
}
- 实现QQ邮箱连接类
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public class QQMailClient {
public void makeClient() {
System.out.println("Connect to Tencent Mail Services...");
}
public void fetchFreshMails() {
System.out.println("Fetch fresh Mails...");
}
public void disConnectFromService() {
System.out.println("Disconnect from the services...");
}
}
- QQ邮箱具体连接与加载类
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public class QQMailAdapter implements EmailProvider {
private final QQMailClient client = new QQMailClient();
@Override
public void downloadEmails() {
client.makeClient();
client.fetchFreshMails();
client.disConnectFromService();
}
}
- 实现Gmail连接类
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public class GmailClient {
public void connect() {
System.out.println("Connecting to Gmail");
}
public void getEmails() {
System.out.println("Downloading emails from Gmail");
}
public void disconnect() {
System.out.println("Disconnecting from Gmail");
}
}
- Gmail具体连接加载类
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public class GmailAdapter implements EmailProvider {
private final GmailClient client = new GmailClient();
@Override
public void downloadEmails() {
client.connect();
client.getEmails();
client.disconnect();
}
}
- 客户端测试类
/**
* Created by Sai
* on: 06/01/2022 16:24.
* Description:
*/
public class Demo {
public static void show() {
var client = new EmailClient();
client.addProvider(new GmailAdapter());
client.addProvider(new QQMailAdapter());
client.downloadEmails();
}
public static void main(String[] args) {
show();
}
}
Connecting to Gmail
Downloading emails from Gmail
Disconnecting from Gmail
----------------------
Connect to Tencent Mail Services...
Fetch fresh Mails...
Disconnect from the services...
----------------------
Process finished with exit code 0
3. 思考
- 设计的原则不应该停留在记忆里,更不是死记硬背,思考加灵活运用到实际问题当中才能更加深刻的理解然后变成自己的东西,这个简单的例子遵循了单一职责原则、依赖倒置原则,当然也是最简单的
适配器模式(Adapter Design Patterns)
。
二、接口隔离原则
一个类不应该依赖它不需要使用的方法(接口),通俗的讲,一个类对另一个类的依赖应该建立在最小的接口之上。单一职责原则同样也规定了单一功能的重要性,可以发现这些设计原则本身都相互关联的。
1.作用
分解臃肿庞大的接口(接口承担的职责太多)为多个粒度更小的接口(提高可维护性、灵活性,Java接口的多实现单继承)。
提高了系统的内聚性,降低了耦合,减少了对外交互。
减少冗余代码(实现了本不需要的方法),结构清晰,提高了可维护性。
2.拆分原则
- 接口隔离粒度是需要仔细细考的,一味的追求小接口数量过多,系统复杂度会显著提升,相反的粒度太大同样达不到降低灵活性的目的。这就要求开发者对业务的抽象能力以及全局思考的能力。一个接口一个模块或者一个子系统。
- 提高内聚,以最少的方法做最多的事情。
- 定制化接口,泛化出接口屏蔽系统不需要的方法,保留仅仅需要的最小方法数(接口基类配置)。
三、迪米特法则
"不跟陌生人说话",设计过程中总是要遵循高内聚、低耦合,各个模块之间只有耦合尽可能低,才能够提高代码的复用性,可读性。结构层次也更加清晰,迪米特法则的另一层含义:只与直接朋友交谈,如果系统之间无需之间通信,则不应该直接发生调用,而应该烦通过第三方(中介)调用,这样自然降低了系统的耦合程度,增强了相对独立性。
1.作用
- 类与类之间尽量不直接通信(中介),降低了系统之间的耦合程度,增强了模块的独立性。
- 低耦合度,提高了系统的可复用性、可扩展性。
同接口隔离一样,“粒度”问题还是需要注意,如果滥用迪米特法则,则会产生大量的“中介类”,系统复杂度反而提高了,项目同样会变得冗余,无论遵循什么样的原则,本质追求的就是使系统高内聚、低耦合,这个才是前提。
2.实现原则
- 降低类成员的访问权限。
- 只暴露该暴露的对外方法,尽可能用少的方法做更多的事情。
- 只依赖应该需要依赖的类,避免实现不必要的空方法。