要学习设计模式,必须要先了解软件设计的六大原则,了解软件设计的底层规律,才不至于设计跑偏
1.单一职责原则
单⼀职责原则(Single Responsibility Principle,SRP)⼜称单⼀功能原则。
1.1.
public class VideoUserService { public void serveGrade(String userType){ if ("VIP用户".equals(userType)){ System.out.println("VIP用户,视频1080P蓝光"); }else if ("普通用户".equals(userType)){ System.out.println("普通用户,视频720P超清"); }else if ("访客用户".equals(userType)){ System.out.println("访客用户,视频480P高清"); } } }
1.2.
public interface IVideoUserService { // 视频清晰级别:480P、720P、1080P void definition(); // 广告播放方式:无广告,有广告 void advertisement(); } //访客逻辑 class GuestVideoUserService implements IVideoUserService{ @Override public void definition() { System.out.println("访客用户,视频480P高清"); } @Override public void advertisement() { System.out.println("访客用户,视频有广告"); } } //普通会员逻辑 class OrdinaryVideoUserService implements IVideoUserService{ @Override public void definition() { System.out.println("普通用户,视频720P超清"); } @Override public void advertisement() { System.out.println("普通用户,视频有广告"); } } //VIP会员逻辑 class VipVideoUserService implements IVideoUserService{ @Override public void definition() { System.out.println("VIP用户,视频1080P蓝光"); } @Override public void advertisement() { System.out.println("VIP用户,视频无广告"); } }
单一职责原则注意事项和细节
1)降低类的复杂度,一个类只负责一项职责
2)提高类的可读性,可维护性
3)降低变更引起的风险
4)通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则
2.接口隔离原则
客户端不应该依赖它不需要的接口,即⼀个类对另⼀个类的依赖应该建⽴在最⼩的接⼝上
接⼝隔离原则(Interface Segregation Principle,ISP)要求程序员尽量将臃肿庞⼤的接⼝拆分成更⼩的和更具体的接⼝,让接⼝中只包含客户感兴趣的⽅法
2.1.正确的做法
ServletContextListener接⼝:
public void contextInitialized(servletContextEvent sce); public void contextDestroyed(servletContextEvent sce);
HttpSessionListener接口:
public void sessionCreated(HttpSessionEvent se); public void sessionDestroyed(HttpSessionEvent se);
ServletRequestListener接⼝:
public void requestInitialized(ServletRequestEvent sre); public void requestDestroyed(ServletRequestEvent sre);
监听应用代码:
public class MyListener implements ServletRequestListener, HttpSessionListener, ServletContextListener { public void contextInitialized(ServletContextEvent arg0) { System.out.println("ServletContext对象被创建了"); } public void contextDestroyed(ServletContextEvent arg0) { System.out.println("ServletContext对象被销毁了"); } public void sessionCreated(HttpSessionEvent arg0) { System.out.println("HttpSession对象被创建了"); } public void sessionDestroyed(HttpSessionEvent arg0) { System.out.println("HttpSession对象被销毁了"); } public void requestInitialized(ServletRequestEvent arg0) { System.out.println("ServletRequest对象被创建了"); } public void requestDestroyed(ServletRequestEvent arg0) { System.out.println("ServletRequest对象被销毁了"); } }
3.依赖倒置原则
- 依赖倒置原则(Dependence Inversion Principle,DIP)是指在设计代码架构时,⾼层模块不应该依赖于底层模块,⼆者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
- 依赖倒置的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
- 使用接口或抽象类的目的是制定要规范,而不设计任何具体的操作,把展现细节的任务交给他们的实现类去完成
- 依赖倒置原则是实现开闭原则的重要途径之一,它降低了类之间的耦合,提高了系统的稳定性和可维护性,同时这样的代码一般更易读,且便于传承
1.1.
抽奖用户类:
public class BetUser { private String userName; //用户姓名 private int userWright; //用户权重 }
抽奖逻辑类:
public class DrawControl { //随机抽取指定数量的用户,作为中奖用户 public List<BetUser> doDrawRandom(List<BetUser> list,int count){ // 集合数量很小直接返回 if (list.size() <= count) return list; // 乱序集合 Collections.shuffle(list); // 取出指定数量的中奖用户 List<BetUser> prizeList = new ArrayList<>(count); for (int i = 0; i < count; i++){ prizeList.add(list.get(i)); } return prizeList; } //权重排名获取指定数量的用户,作为中奖用户 public List<BetUser> doDrawWeight(List<BetUser> list, int count) { // 按照权重排序 list.sort((o1, o2) -> { int e = o2.getUserWeight() - o1.getUserWeight(); if (0 == e) return 0; return e > 0 ? 1 : -1; }); // 取出指定数量的中奖⽤户 List<BetUser> prizeList = new ArrayList<>(count); for (int i = 0; i < count; i++) { prizeList.add(list.get(i)); } return prizeList; } }
2.1.正确的做法
抽奖接口:
public interface IDraw { //获取中奖用户接口 List<BetUser> prize(List<BetUser> list, int count); }
随机抽奖实现:
public class DrawRandom implements IDraw{ @Override public List<BetUser> prize(List<BetUser> list, int count) { // 集合数量很小直接返回 if (list.size() <= count ) return list; // 乱序集合 Collections.shuffle(list); // 取出指定数量的中奖用户 List<BetUser> prizeList = new ArrayList<>(count); for (int i = 0; i < count; i++){ prizeList.add(list.get(i)); } return prizeList; } }
权重抽奖实现:
public class DrawWeightRank implements IDraw{ @Override public List<BetUser> prize(List<BetUser> list, int count) { // 按照权重排序 list.sort((o1, o2) -> { int e = o2.getUserWeight() - o1.getUserWeight(); if (0 == e) return 0; return e > 0 ? 1 : -1; }); // 取出指定数量的中奖用户 List<BetUser> prizeList = new ArrayList<>(count); for (int i = 0; i < count; i++){ prizeList.add(list.get(i)); } return prizeList; } }
开奖:
public class DrawControl { public List<BetUser> doDraw(IDraw draw, List<BetUser> betUserList,int count){ return draw.prize(betUserList, count); } public static void main(String[] args) { List<BetUser> userList = new ArrayList<>(); // 初始化userList // 这里的重点是把实现逻辑的接口作为参数传递 new DrawControl().doDraw(new DrawWeightRank(), userList,3); } }
:
1)底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
2)变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
3)继承时遵循里氏替换原则
4.里式替换原则
继承必须确保超类所拥有的性质在⼦类中仍然成⽴。
简单来说,子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:当子类继承父类时,除添加新的方法且完成新增功能外,尽量不要重写父类的方法。这句话包括了四点含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
- 当子类的方法实现父类的方法(重写、重载或实现抽象方法)时,方法的后置条件(即方法的输出或返回值)要比父类的方法更严格或父类的方法相等。
1. 继承作为⾯向对象的重要特征,虽然给程序开发带来了⾮常⼤的便利,但也引⼊了⼀些弊端。继承的开发⽅式会给代码带来侵⼊性,可移植能⼒降低,类之间的耦合度较⾼。当对⽗类修改时,就要考虑⼀整套⼦类的实现是否有⻛险,测试成本较⾼。
2. ⾥⽒替换原则的⽬的是使⽤约定的⽅式,让使⽤继承后的代码具备良好的扩展性和兼容性。在⽇常开发中使⽤继承的地⽅并不多,在有些公司的代码规范中也不会允许多层继承,尤其是⼀些核⼼服务的扩展。⽽继承多数⽤在系统架构初期定义好的逻辑上或抽象出的核⼼功能⾥。如果使⽤了继承,就⼀定要遵从⾥⽒替换原则,否则会让代码出现问题的概率变得更⼤。
3. 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了。在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法,在适当的情况下,可 以通过聚合,组合,依赖 来解决问题。
1.1.
储蓄卡类:
public class CashCard { private Logger logger = LoggerFactory.getLogger(CashCard.class); public String withdrawal(String orderId, BigDecimal amount) { //模拟支付成功 logger.info("提现成功,单号:{} 金额 {}", orderId, amount); return "0000"; } public String recharge(String orderId, BigDecimal amount) { //模拟充值成功 logger.info(" {} {}", orderId, amount); return "0000"; } public List<String> tradeFlow(){ logger.info("交易流水查询成功"); List<String> tradeList = new ArrayList<String>(); tradeList.add("100001,100.00"); tradeList.add("100001,80.00"); tradeList.add("100001,76.50"); tradeList.add("100001,126.00"); return tradeList; } }
信用卡类:
public class CreditCard extends CashCard{ private org.slf4j.Logger logger = LoggerFactory.getLogger(CashCard.class); @Override public String withdrawal(String orderId, BigDecimal amount) { //校验 if (amount.compareTo(new BigDecimal(1000)) >= 0){ logger.info("贷款金额校验(限额1000元),单号:{} 金额:{}",orderId,amount); return "0001"; } //模拟生成贷款单 logger.info("生成贷款单,单号:{} 金额:{}",orderId, amount); //模拟支付成功 logger.info("贷款成功,单号:{} 金额:{}",orderId, amount); return "0000"; } @Override public String recharge(String orderId, BigDecimal amount) { // 模拟生成还款单 logger.info("生成还款单,单号:{} 金额:{}", orderId,amount); // 模拟还款成功 logger.info("还款成功,单号:{} 金额:{}",orderId,amount); return "0000"; } @Override public List<String> tradeFlow() { return super.tradeFlow(); } }
1.2.
银行卡类:
public abstract class BankCard { private Logger logger = LoggerFactory.getLogger(BankCard.class); private String cardNo; private String cardDate; public BankCard(String cardNo, String cardDate) { this.cardNo = cardNo; this.cardDate = cardDate; } abstract boolean rule(BigDecimal amount); //正向入账,+ 钱 public String positive(String orderId,BigDecimal amount){ //入账成功,存款、还款 logger.info("卡号{} 入款成功,单号:{} 金额:{}",cardNo,orderId,amount); return "0000"; } //逆向入账,-钱 public String negative(String orderId,BigDecimal amount){ logger.info("卡号{} 出款成功,单号:{} 金额:{}",cardNo,orderId,amount); return "0000"; } /* * 交易流水查询 * @return 交易流水 */ public List<String> tradeFlow(){ logger.info("交易流水查询成功"); List<String> tradeList = new ArrayList<String>(); tradeList.add("100001,100.00"); tradeList.add("100001,80.00"); tradeList.add("100001,76.50"); tradeList.add("100001,126.00"); return tradeList; } public String getCardNo(){ return cardNo; } public String getCardDate(){ return cardDate; } }
储蓄卡类:
public class CashCard extends BankCard{ private Logger logger = LoggerFactory.getLogger(CashCard.class); public CashCard(String cardNo, String cardDate) { super(cardNo, cardDate); } @Override boolean rule(BigDecimal amount) { return false; } public String withdrawal(String orderId, BigDecimal amount){ //模拟支付成功 logger.info("提现成功,单号:{} 金额:{}",orderId,amount); return super.negative(orderId,amount); } public String recharge(String orderId,BigDecimal amount){ //模拟充值成功 logger.info("储蓄成功,单号:{} 金额:{}",orderId,amount); return super.positive(orderId,amount); } public boolean checkRisk(String cardNo,String orderId,BigDecimal amount){ //模拟风控校验 logger.info("风控校验,卡号:{} 单号:{} 金额:{}",cardNo,orderId,amount); return true; } }
信用卡类:
public class CreditCard extends CashCard{ private Logger logger = LoggerFactory.getLogger(CreditCard.class); public CreditCard(String cardNo, String cardDate) { super(cardNo, cardDate); } boolean rule2(BigDecimal amount) { return amount.compareTo(new BigDecimal(1000)) <= 0; } public String loan(String orderID,BigDecimal amount){ boolean rule = rule2(amount); if (!rule){ logger.info("生成贷款单失败,金额超限。单号:{} 金额:{}",orderID,amount); return "0001"; } //模拟生成贷款单 logger.info("生成贷款单,单号:{} 金额:{}",orderID,amount); //模拟支付成功 logger.info("贷款成功,单号:{} 金额:{}",orderID,amount); return super.negative(orderID,amount); } public String repayment(String orderId,BigDecimal amount){ //模拟生成还款单 logger.info("生成还款单,单号:{} 金额:{}",orderId,amount); //模拟还款成功 logger.info("还款成功,单号:{} 金额:{}",orderId,amount); return super.positive(orderId,amount); } }
5.开闭原则
在⾯向对象编程领域中,开闭原则规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的。
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
开闭原则的核⼼思想也可以理解为⾯向抽象编程。
5.1.错误的做法
public class Ocp { public static void main(String[] args) { // 使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); } } //这是一个用于绘图的类(使用方) class GraphicEditor { //接收Shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape s) { if (s.m_type == 1) drawRectangle(s); else if (s.m_type == 2) drawCircle(s); else if (s.m_type == 3) drawTriangle(s); } //绘制矩形 public void drawRectangle(Shape r) { System.out.println("绘制矩形"); } //绘制圆形 public void drawCircle(Shape r) { System.out.println("绘制圆形"); } //绘制三角形 public void drawTriangle(Shape r) { System.out.println("绘制三角形"); } } //Shape类,基类 class Shape { int m_type; } class Rectangle extends Shape { Rectangle() { super.m_type = 1; } } class Circle extends Shape { Circle() { super.m_type = 2; } } //绘制三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } }
1.2.
public class Ocp { public static void main(String[] args) { //使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); graphicEditor.drawShape(new OtherGraphic()); } } //这是一个用于绘图的类【使用方】 class GraphicEditor { //接收Shape对象,调用draw方法 public void drawShape(Shape s) { s.draw(); } } //shape类,基类 abstract class Shape { int m_type; public abstract void draw();//抽象方法 } class Rectangle extends Shape { Rectangle() { super.m_type = 1; } @Override public void draw() { // TODO Auto-generated method stub System.out.println("绘制矩形"); } } class Circle extends Shape { Circle() { super.m_type = 2; } @Override public void draw() { // TODO Auto-generated method stub System.out.println("绘制圆形"); } } //新增画三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } @Override public void draw() { // TODO Auto-generated method stub System.out.println("绘制三角形"); } } //新增一个图形 class OtherGraphic extends Shape { OtherGraphic() { super.m_type = 4; } @Override public void draw() { // TODO Auto-generated method stub System.out.println("绘制其他图形"); } }
6.迪米特法则
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
- 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系, 我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而 出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量 的形式出现在类的内部。
5.1.错误的做法
老师需要负责具体某⼀个学⽣的学习情况,⽽校⻓会关⼼⽼师所在班级的总体成绩,不会过问具体某⼀个学⽣的学习情况。
学生类:
public class Student { private String name; // 学生姓名 private int rank; // 考试排名(总排名) private double grade; // 考试分数(总分) public Student() { } public Student(String name, int rank, double grade) { this.name = name; this.rank = rank; this.grade = grade; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getRank() { return rank; } public void setRank(int rank) { this.rank = rank; } public double getGrade() { return grade; } public void setGrade(double grade) { this.grade = grade; } }
老师类:
public class Teacher { private String name; // 老师名称 private String clazz; // 班级 private static List<Student> studentList; //学生 public Teacher() { } public Teacher(String name, String clazz) { this.name = name; this.clazz = clazz; } static { studentList = new ArrayList<>(); studentList.add(new Student("花花", 10, 589)); studentList.add(new Student("豆豆", 54, 356)); studentList.add(new Student("秋雅", 23, 439)); studentList.add(new Student("皮皮", 2, 665)); studentList.add(new Student("蛋蛋", 19, 502)); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getClazz() { return clazz; } public void setClazz(String clazz) { this.clazz = clazz; } public static List<Student> getStudentList() { return studentList; } public static void setStudentList(List<Student> studentList) { Teacher.studentList = studentList; } }
校长管理类:
public class Principal { private Teacher teacher = new Teacher("丽华","3年1班"); //查询班级信息,总分数,学生人数,平均值 public Map<String,Object> queryClazzInfo(String clazzId){ // 获取班级信息:学生总人数、总分、平均分 int stuCount = clazzStudentCount(); double totalScore = clazzTotalScore(); double averageScore = clazzAverageScore(); //组装对象,实际业务开发会有对应的类 Map<String,Object> mapObj = new HashMap<>(); mapObj.put("班级",teacher.getClazz()); mapObj.put("老师",teacher.getName()); mapObj.put("学生人数",stuCount); mapObj.put("班级总分数",totalScore); mapObj.put("班级平均分",averageScore); return mapObj; } //总分 public double clazzTotalScore(){ double totalScore = 0; for (Student stu : teacher.getStudentList()){ totalScore += stu.getGrade(); } return totalScore; } //平均分 public double clazzAverageScore(){ double totalScore = 0; for (Student stu : teacher.getStudentList()){ totalScore += stu.getGrade(); } return totalScore / teacher.getStudentList().size(); } //班级人数 public int clazzStudentCount(){ return teacher.getStudentList().size(); } }
2.1.正确的做法
由老师负责统计分数:
public class Teacher { private String name; // ⽼师名称 private String clazz; // 班级 private static List<Student> studentList; // 学⽣ public Teacher() { } public Teacher(String name, String clazz) { this.name = name; this.clazz = clazz; } static { studentList = new ArrayList<>(); studentList.add(new Student("花花", 10, 589)); studentList.add(new Student("⾖⾖", 54, 356)); studentList.add(new Student("秋雅", 23, 439)); studentList.add(new Student("⽪⽪", 2, 665)); studentList.add(new Student("蛋蛋", 19, 502)); } // 总分 public double clazzTotalScore() { double totalScore = 0; for (Student stu : studentList) { totalScore += stu.getGrade(); } return totalScore; } // 平均分 public double clazzAverageScore(){ double totalScore = 0; for (Student stu : studentList) { totalScore += stu.getGrade(); } return totalScore / studentList.size(); } // 班级⼈数 public int clazzStudentCount(){ return studentList.size(); } public String getName() { return name; } public String getClazz() { return clazz; } }
校⻓只负责从⽼师哪⾥收集信息即可,并不需要获取具体学⽣信息:
public class Principal { private Teacher teacher = new Teacher("丽华", "3年1班"); // 查询班级信息,总分数、学⽣⼈数、平均值 public Map<String, Object> queryClazzInfo(String clazzId) { // 获取班级信息;学⽣总⼈数、总分、平均分 int stuCount = teacher.clazzStudentCount(); double totalScore = teacher.clazzTotalScore(); double averageScore = teacher.clazzAverageScore(); // 组装对象,实际业务开发会有对应的类 Map<String, Object> mapObj = new HashMap<>(); mapObj.put("班级", teacher.getClazz()); mapObj.put("⽼师", teacher.getName()); mapObj.put("学⽣⼈数", stuCount); mapObj.put("班级总分数", totalScore); mapObj.put("班级平均分", averageScore); return mapObj; } }
7.设计原则核心思想
1)找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
2)针对接口编程,而不是针对实现编程
3)为了交互对象之间的松耦合设计而努力