参考资料
参考资料:视频资料
面向对象设计,Object Oriented Design,简称OOD。
在进行软件开发时,需要考虑项目的可维护性和可复用性,开发项目一般是由一个开发团队来维护,因此我们在编写代码时,应可能规范,防止项目出现越来越多的BUG。
一、单一职责原则 SRP
参考资料:查看文档
单一职责原则, Single Responsibility Principle 又称单一功能原则,是最简单的面向对象设计原则,用于控制类的粒度大小。
出自《敏捷软件开发:原则、模式与实践》 Robert C. Martin
一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中
例:用类描述人们的会做的事情
- 反例:违背 SRP 单一职责原则
public class People{
// 程序员: 敲代码
public void coding(){}
// 飞行员: 驾驶飞机
public void pilot(){}
}
类中出现了范围偏多的敲代码和驾驶飞机,实际上,生活中很少有人同时具备这两项技能,故根据 SRP 原则,上述的类应变更为:
- 正例:符合 SRP 原则
// 程序员
class Programmer{
public void coding(){}
}
// 飞行员
class Piloter{
public void pilot(){}
}
总结:SRP 单一职责原则 要求在设计类的时候,里面的方法或属性是同一类的,颗粒度尽可能小的。
二、开闭原则 OCP
参考资料:查看文档
开闭原则,Open Close Principle,OCP 原则规定软件中的对象(类、模块、函数等)应对扩展开放,对修改关闭。
出自:《面向对象软件构造》
软件对扩展(提供方)开放,对修改(调用方)关闭
正例:用类描述不同编程语言领域的程序员
public abstract class Programmer{
public abstract void Codding();
}
// Java程序员
class JavaProgrammer extends Programmer{
@Override
public void coding(){}
}
// Python 程序员
class PythonProgrammer extends Programmer{
@Override
public void coding(){}
}
// C++语言程序员
class CppProgrammer extends Programmer{
@Override
public void coding(){}
}
通过提供一个 Programmer 抽象类,定义出编程的抽象行为,这样给其他具体类型的程序员来实现,这样就可以根据不同的业务进行灵活扩展,具有更好的扩展性。除了抽象类,更多的则是使用接口和接口实现类来遵循OCP原则
三、里氏替换原则 LSP
参考资料:查看文档
里氏替换原则,Liskov Substitution Principle,LSP 原则是对子类型的特别定义。
出自:演讲《数据的抽象与层次》 Barbara Liskov
所有引用基类的地方必须能透明的使用其子类的对象
简单来说,子类可以扩展父类的功能,但不能改变父类原有的功能:
- 子类可以实现父类的抽象方法,但不能覆盖父类原有的功能
- 子类可以增加自己持有的方法
- 子类重载父类方法时,方法的前置条件(即方法的入参)要比父类的该方法入参更宽松
- 子类实现父类方法时,(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样
例: 描述一个Java程序员会做的事情
反例:违背 LSP 原则
public abstract class Programmer{
public void coding(){}
}
// 描述会经常运动的Java程序员
class JavaProgrammer extends Programmer{
// 违背 LSP 原则,重写了父类非抽象的方法。
public void codding(){}
// 拓展功能: 做运动
public void doSport(){}
}
正确例子:遵循 以上三种原则
public abstract class Programmer{
public abstract void coding(){}
// 程序员都需要休息
public void sleep(){}
}
// 描述会经常运动的Java程序员
class JavaProgrammer extends Programmer{
// public void sleep() , 遵循 LSP原则,不覆盖原有的功能, 除非sleep()为抽象的方法
// 重写父类方法
@Override
public void codding(){}
// 拓展功能: 做运动
public void doSport(){}
}
四、依赖倒转原则 DIP
依赖倒转原则,Dependence Inversion Procinple,DIP原则指出程序要依赖抽象接口,不要依赖于具体实现。这在Spring框架中得到了广泛运用。
高层模块不应依赖于底层模块。抽象不应依赖于细节,细节应依赖于抽象。
以 JavaEE开发中,传统的MVC架构为例:
反例:违背了 DIP 原则
public class Solution{
public static void main(String[] args){
UserController controller = new UserController();
// ... 具体的应用
}
static class UserMapper {
// CRUD
}
static class UserService{
UserMapper mapper = new UserMapper();
// 业务层代码
}
static class UserController{
UserServiceservice = new UserService();
// 控制层代码
}
}
在上述代码中,假如UserMapper() 发生变更,那么 类UserService就需要进行重构,由于UserController依赖于UserService,故UserController也需要进行重构。
public class Solution{
public static void main(String[] args){
UserController controller = new UserController();
// ... 具体的应用
}
static class UserMapperNew {
// CRUD
}
static class UserService{
// mapper 发生改变,业务层代码需进行重构
UserMapperNew mapper = new UserMapperNew();
// 业务层代码
}
static class UserController{
// service 发生改变,控制层代码需进行重构
UserServiceservice = new UserService();
// 控制层代码
}
}
综上,在传统MVC架构中,Controller模块依赖于 Service模块,而Service模块依赖于Mapper模块,这样一来结构清晰,但底层模块的变动,会直接影响其他依赖于该模块的高层模块,以下是Spring框架中的代码,高内聚,低耦合:
public class Solution{
public static void main(String[] args){
UserController controller = new UserController();
// ... 具体的应用
}
interface UserMapper {
// 接口声明CRUD方法
}
@Mapper
static class UserMapperImpl implements UserMapper{
// 实现类完成CURD具体实现
}
interface UserService{
// 接口声明业务层方法
}
@Service
class UserServiceImpl implements UserService{
// 实现类完成业务层具体实现
}
@Controller
class UserController{
@Resource
private UserService service; // 直接引入Spring IOC 容器中的接口
// 控制层代码
}
}
通过上述代码可以看出,我们可以将原有的强关联弱化,只需要知道接口中定义了什么方法然后去使用即可,而具体的操作由接口的实现类来完成,并由Spring注入接口的实现类,而不是通过之前硬编码的方式去定义。
五、接口隔离原则 ISP
参考资料:查看文档
接口隔离原则,Interfacce Segregation Principle ,ISP 原则是对接口的细化。
客户端不应依赖那些它不需要的接口
例:在定义接口的时候,一定要注意控制接口的粒度
// 表示设备的接口
interface Device{
String getCpu();
String getType();
String getMemory();
}
// 电脑设备
class Computer implements Device{
@Override
public String getCpu(){
return "i5";
}
@Override
public String getType(){
return "电脑";
}
@Override
public String getMemory(){
return "16G";
}
}
// 电风扇设备
class Fan implements Device{
@Override
public String getCpu(){
return null;
}
@Override
public String getType(){
return "风扇";
}
@Override
public String getMemory(){
return null;
}
}
如上述代码,定义的 Device 接口粒度不够细,不能适用于多种设备。故需要再进行划分,如下所示:
interface SmartDevice{ // 智能设备
String getCpu();
String getType();
String getMemory();
}
interface NormalDevice{ // 普通设备
String getType();
}
class Computer implements SmartDevice{
...
}
class Fan implements NomarlDevice{
...
}
如此一来,接口更加细化,符合了ISP 的设计原则。
六、合成复用原则 CRP
合成复用原则,Composite Reuse Principle ,CRP 原则的核心是委派。
优先使用对象组合,而不是通过继承来达到复用的目的
如果定义的类C需要用到类A的功能,那么应该优先考虑使用 **合成 **的方式来实现复用。
反例:使用继承来实现复用
class A{
public void connect(){ }
}
class C extends A{
public void test(){
connnect();
}
}
存在的问题:
- 代码耦合度过高,若类A发生改变,那么类C需要进行重构。
- 安全性低,类C将会拥有类A所有的方法和变量,不安全。
遵循 合成服用原则(CRP)后:
class A{
public void connect(){ }
}
class C {
public void test(A a){
a.connnect();
}
}
或者是将类A定义到类C中:
class A{
public void connect(){ }
}
class C {
A a;
public C(A a){
this.a = a;
}
public void test(){
a.connnect();
}
}
通过对象之间的组合,降低了类之间的耦合度。
七、迪米特法则 LOD
迪米特法则,Law of Demeter ,LOD法则又称最少知识原则,是对程序内部数据交互的限制。
每一个软件单位对其他单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位。
简单说,一个类 / 模块对其他的类 / 模块之间交互越少越好。当一个类发生改动,与之相关的类会受到影响,所以交互越少越好,便于降低耦合度。
public class Main{
public static void main(String[] args) throws IOException{
Socket socket = new Socket("localhost", 8080);
new Test().test(socket);
}
static class Test{
public void test(Socket socket){
System.out.println(socket.getLocalAddress());
}
}
}
上面这种写法每问题,但是在调用 test方法时,需要传入一个Socket对象,在这里我们的逻辑只是输出socket的IP地址,不符合 LOD 设计原则,可以简化如下:
public class Main{
public static void main(String[] args) throws IOException{
Socket socket = new Socket("localhost", 8080);
new Test().test(socket.getLocalAddress());
}
static class Test{
public void test(String str){
System.out.println(str);
}
}
}
八、总结
通过本次学习,我了解了面向对象的七大设计原则,对我来说,一次性记住是不太现实的,需要在平时编码里时刻注意,比如在完成一段代码后,可以思考一下这个是关于什么设计原则的,或者在看框架源码时,去体会源码设计的方式,从而更深地理解七大设计原则,接下来我将学习23种设计模式,希望通过这样的方式,能提升一定的编码水平。