设计原则与思想:设计原则
理论一:单一职责原则
1.如何理解单一职责原则?
- What:一个类只负责完成一个职责或者功能
- How:不要设计大而全的类->要设计粒度小、功能单一的类
- Why:实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性
2.如何判断类的职责是否足够单一?
不同场景的判断标准不同,下面这些情况就有可能说明这类的设计不满足单一职责原则(不是绝对的):- 类中代码行数过多、函数或者属性过多
- 类依赖或被依赖的其他类的过多
- 私有(
private
)方法过多->考虑抽取私有方法到新的类中并设置为public方法 - 类难命名(如只能命名为一些笼统的词语如Manager、Context等)
- 类大量方法都是集中操作类中的某几个属性(如用户信息UserInfo表中大部分在操作地址address信息则可以考虑抽出来)
public class UserInfo { //用户基础信息 private long uid;//用户唯一id private String username;//用户昵称 private int age;//用户年龄 private Date createTime;//用户注册时间 private Date lastLoginTime;//用户最后登陆时间 private String avatarUrl;//用户头像 //地址相关信息 private String provinceOfAddress;//省 private String cityOfAdddress;//市 private String regionOfAddress;//区 private String detailedAddress;//详细地址 }
3.类的职责是否设计得越单一越好?
如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性
理论二:开闭原则(对扩展开放、修改关闭)
1.如何理解”对扩展开放、修改关闭”?
添加一个新的功能,
应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等), //对扩展开发
而非修改已有代码(修改模块、类、方法、属性) //对修改关闭
2.如何做到”对扩展开发、修改关闭”?
- 时刻具备扩展意识、抽象意识、封装意识
- 写代码时候思考未来可能有哪些需求变更
- 思考如何设计代码结构,事先留好扩展点
- 保证在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,插入新的代码
3.常见的提高代码扩展性的方法
- 多态
- 依赖注入(DI)
- 基础接口而非实现编程
- 大部分的设计模式(如装饰、策略、模板、责任连、状态)
理论三:里式替换原则(LSP)
- What:
里式替换原则
是用来指导,继承关系中子类该如何设计的一个原则。(design by contract,按照协议来设计) - How:父类定义了函数的”约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的”约定”。这里的约定包括:
- 函数声明要实现的功能 //比如父类中提供了
sortOrdersByAmount()
订单排序函数是按照金额从小到大排序的,而如果子类重写该函数是按照日期(而非原本的金额)来排序,那么就是违背里式替换原则
- 对输入、输出、异常的约定 //比如父类约定运行出错返回null,而子类运行出错抛异常,那么就是违背
里式替换原则
- 注释罗列的任何特殊说明 //比如父类
withdraw()
提醒函数写了注释”用户提现金额不得超过账户余额”,而子类重写该函数后针对VIP账号实现了透支提现的功能(也就是提醒金额可以大于账户金额),那么就是违背里式替换原则
- 函数声明要实现的功能 //比如父类中提供了
- Why:
里式替换原则
是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。
理论四:接口隔离原则(ISP)
- What:
接口隔离原则
即”Interface Segregation Principle”(缩写为ISP)。直白来说,就是客户端不应该强迫依赖它不需要的接口。 - Why:
- 防止误操作(如误删用户)
- 更细粒度,会更加灵活、易扩展、易复用
- 调用方无需引用+理解没必要的接口
- Example:
- 不使用ISP时
public interface UserService { //注册 boolean register(User user); //登陆 boolean login(User user); //查询用户 User getUserByUid(long uid); //删除(敏感操作,理论上只能在后台操作) boolean deleteUserByUid(long uid); } public class UserServiceImpl implements UserService { //... }
- 使用ISP时
public interface UserService { //注册 boolean register(User user); //登陆 boolean login(User user); //查询用户 User getUserByUid(long uid); } //为了防止误操作,我们应该把用户操作的API和后台操作的API隔离开(为了防止调用UserService的误使用deleteUserByUid引起安全隐患) public interface AdminUserService { //删除(敏感操作,理论上只能在后台操作) boolean deleteUserByUid(long uid); } public class UserServiceImpl implements UserService,AdminUserService { //... }
- 不使用ISP时
理论五:依赖反转原则(DIP)
1.控制反转(IOC)
控制:指的是对程序执行流程的控制
反转
没使用框架前:程序员自己控制整个程序的执行
public class UserServiceTest { public static boolean doTest() { //... } public static void main(String[] args) { if(doTest()){ System.out.println("success"); } else { System.out.println("fail"); } } }
使用框架后:整个程序的执行流程通过框架来控制(流程的控制权从程序员
反转
给了框架)public abstract class TestCase { public void run() { if (doTest()) { System.out.println("success"); } else { System.out.println("fail"); } } public abstract boolean doTest(); } //我们只需要在框架预留的扩展点(也就是TestCase类中的doTest()抽象函数中填充具体测试代码就能模拟单元测试了) public class UserServiceTest extends TestCase { @Override public boolean doTest() { //... } } //抽象出来的框架 public class JUnitApplication { private static final List<TestCase> testCases = new ArrayList<>(); public static void register(TestCase testCase) { testCases.add(testCase); } public static void main(String[] args) { //添加需要测试的类 JUnitApplication.register(new UserServiceTest()); //控制反转式"测试" for (TestCase testCase : testCases) { testCase.run(); } } }
2.依赖注入(DI)
What:不通过
new
的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好,通过构造函数等方式传递(或注入)给类使用How:通过一个邮箱系统来讲解
不使用DI
//邮箱 public class MailBox { //发送邮件的工具 MailSender mailSender; public MailBox() { this.mailSender = new MailSender(); } } public class TestApplication { //构造MailBox的时候内部会自己new一个MailSender MailBox mailBox = new MailBox(); }
使用DI
//邮箱 public class MailBox { //发送邮件的工具 MailSender mailSender; public MailBox(MailSender mailSender) { this.mailSender = mailSender; } } public class TestApplication { //先初始化内部依赖的对象 MailSender mailSender = new MailSender(); //再通过构造方法传递 MailBox mailBox = new MailBox(mailSender); }
3.依赖注入框架(DI Framework)
- 简单配置一下所有需要的类及其类与类之间依赖关系,就可以实现如下内容(原本需要程序员做的事情)
- 由框架来自动创建对象
- 由框架来管理对象的生命周期
- 由框架来管理对象的依赖注入
4.依赖反转原则(DIP)
- What:
依赖反转原则
也叫做依赖倒置原则
- 用来指导框架层面的设计
- How:
- 高层模块不依赖低层模块,它们共同依赖同一个抽象
- 抽象不要依赖具体实现细节,具体实现细节依赖对象
- Example:
- Tomcat是运行Java Web应用程序的容器
- 应用程序可以部署在Tomcat容器上,便可被Tomcat容器调用执行
- Tomcat是高层模块,应用程序是低层模块
- Tomcat和应用程序代码之间没有直接的依赖关系,两者都依赖同一个”抽象”(也就是Servlet规范)
- Servlet规范不依赖具体的Tomcat容器和应用程序的实现细节,而Tomcat容器和应用程序依赖Servlet规范