单一职责原则 SRP

一个类只有一个引起修改变化的原因,也就是只负责一个职责。核心思想:高内聚,低耦合

假设一个类有多个功能,当修改其中一个功能的时候,可能会对其他功能造成影响

优点:

1:降低类的复杂度,一个类负责一个职责,比负责多项职责要简单

2:代码的可读性提高了,也方便以后的维护

下面举例子说明,小明要学习一些课程,每个课程都有不同的知识点,JAVA学习面向对象,MYSQL学习sql语句

先建一个学习的类study:

public class Study {
public void studyCourse(String courseName){
if ("JAVA".equals(courseName)) {
System.out.println("小明在学习面向对象,new了一个girl");
} else if ("MYSQL".equals(courseName)) {
System.out.println("小明在学习mysql,查询girl没有查询到");
}
}
}

然后建一个测试类test:

public static void main(String[] args) {
Study study = new Study();
study.studyCourse("JAVA");
}

下面老师说了,我们马上要学习html了,怎么去修改代码呢,在加一个else if当然没有问题,可是随着课程的变化和增加,study类会越来越庞大,负责的职责越来越多,后期的维护和修改可能都会比较麻烦



那么,下面进行下修改,把每个课程提取出来:

public class JavaCourse {
public void studyJava(){
System.out.println("小明在学习面向对象,new了一个girl");
}
}
public class MysqlCourse {
public void studyMysql(){
System.out.println("小明在学习mysql,查询girl没有查询到");
}
}

再次修改测试类:

public static void main(String[] args) {
JavaCourse javaCourse = new JavaCourse();
javaCourse.studyJava(); MysqlCourse mysqlCourse = new MysqlCourse();
mysqlCourse.studyMysql();
}

这样每个类负责的职责就只有一个了,可是现在又增加需求了,学习完了要测验学习效果,小明需要交作业了,每个课程都要交一份作业,怎么做,在当前的类中加一个交作业的方法是可以的,还有更好的办法吗



可以把学习和作业抽象出来,建一个接口,然后每个课程自己去实现:

接口Course:学习接口

public interface Course {
public void study(String studentName);
}

接口HomeWork:作业接口

public interface HomeWork {
public void homeWork(String studentName);
}

JavaCourse类:

public class JavaCourse implements Course {
@Override
public void study(String studentName) {
System.out.println(studentName + "在学习面向对象,new了一个girl");
}
}

JavaHomeWork类:

public class JavaHomeWork implements HomeWork {
@Override
public void homeWork(String studentName) {
System.out.println(studentName + "交了一份java作业");
}
}

MysqlCourse类:

public class MysqlCourse implements Course {
@Override
public void study(String studentName) {
System.out.println(studentName + "在学习mysql,查询girl没有查询到");
}
}

MysqlHomeWork类:

public class MysqlHomeWork implements HomeWork {
@Override
public void homeWork(String studentName) {
System.out.println(studentName + "交了一份mysql作业");
}
}

类图:



修改之后,学习接口只负责学习相关的功能,作业接口只负责作业相关的功能,方便维护和修改,是不是更加清晰了呢

开闭原则 OCP

对模块类的操作,对修改关闭,对扩展开放,多年验证的代码,可能你一修改就是Bug

这个原则说的是,在设计一个模块的时候,应当使这个模块在不被修改的情况下被扩展,换而言之,可以在不修改源代码的情况下改变这个模块的行为

优越性:

1:通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新的需求,使得软件有一定的适应性和灵活性

2:已经有的软件模块,特别是比较重要的抽象模块不能再被修改,这就使得变化中的软件有一定的稳定性和延续性

怎样做到开闭原则:

1:抽象化

2:对可变性的封装

下面举例子说明:



商家卖苹果,平时的价格都不会发生变化,到了一些特定的节日就会打折或者买三斤送一斤,下面根据这个场景写代码:

SellApple接口:

public interface SellApple {

    String getName();

    BigDecimal getPrice();
}

Apple类:平时的苹果价格从这个类获取

public class Apple implements SellApple {
private String name; private BigDecimal price; public Apple(String name, BigDecimal price) {
this.name = name;
this.price = price;
} @Override
public String getName() {
return this.name;
} @Override
public BigDecimal getPrice() {
return this.price;
}
}

如果我们直接修改apple这个里面的getPrice()方法,可能会对其他用到这个类的方法造成影响,那么我们重新建一个类来提供一个打折的方法:

public class SaleApple implements SellApple {
private String name; private BigDecimal price; public SaleApple(String name, BigDecimal price) {
this.name = name;
this.price = price;
} @Override
public String getName() {
return this.name;
} @Override
public BigDecimal getPrice() {
return this.price.multiply(BigDecimal.valueOf(0.8));
}
}

这样修改之后不会影响到apple类中的价格,也扩展了我们现在需要用到的价格,这就是简单的开闭原则的使用

类图:



如果后面还有其他的价格优惠,可以直接再实现接口即可

里氏替换原则 LSP

超类可以被替换为其子类来使用,这样就能做到代码不用修改,能够任意切换不同实现的子类,少修改复用

里氏替换原则严格来表达是:

如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型

换而言之,一个软件实体如果使用的是一个基类的话,那么一定适用其子类,而且它根本不能察觉出基类对象和子类对象的区别

里氏替换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不会受到影响时,基类才能真正被复用,而衍生类也才能够在基类的基础上增加新的行为



白马与马:

《墨子 小取》中说到:白马,马也;乘白马,乘马也。骊马,马也;乘骊马,乘马也

这个的意思就是说不论白马、黑马都是马,那么马可以骑,当然白马、黑马都可以骑



在这里,马就是抽象的马,白马和黑马是具体的子类,一匹白马和一匹黑马是对应的实例,下面从代码角度看:

Horse类:

public class Horse {
public void rideHorse() {
System.out.println("骑马");
}
}

WhiteHorse类:

public class WhiteHorse extends Horse {
@Override
public void rideHorse() {
System.out.println("骑白马");
}
}

BlackHorse类:

public class BlackHorse extends Horse {
@Override
public void rideHorse() {
System.out.println("骑黑马");
}
}

下面进行测试:

public static void main(String[] args) {
ride(new Horse());
ride(new WhiteHorse());
ride(new BlackHorse());
} public static void ride(Horse horse) {
horse.rideHorse();
}

可以看到,ride方法接收的参数是Horse,那么WhiteHorse和BlackHorse也可以接收,但是反过来的代换则不成立

public static void rideHorse(WhiteHorse horse) {
horse.rideHorse();
}

上面的代码如果传Horse则会报错

类图:

依赖倒置原则 DIP

程序依赖于抽象接口,不依赖于具体实现,降低实现模块间的耦合度,你不要来找(new)我,我会来找(set)你

依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体

什么是依赖倒转原则:

1:抽象不应该依赖于细节,细节应该依赖于抽象

2:要针对接口编程,不要针对实现编程

抽象层次包含的是应用系统的商务逻辑以及对整个系统来说非常重要的决定,是必然性的体现;而具体层次则包含了一些与算法和实现相关的逻辑,具体层次的代码是会经常变动的,如果抽象层级依赖具体层次,就是微观影响了宏观,这是很荒唐的



下面从一个例子入手:

淘宝、京东双11大促销,有很多的优惠活动,比如满减、打折等,顾客可以从中选择多个优惠活动来买买买

下面是一个商品,有满减和打折活动

public class Order {

    public void fullReduction() {
System.out.println("满500减100");
} public void reducedRate() {
System.out.println("满3件打7折");
}
}
public static void main(String[] args) {
Order order = new Order();
order.fullReduction();
order.reducedRate();
}

由于商品买的人较多,商家想出一个新的活动,每满200元减100和满3件最后一件最低价,那么要实现的话,就需要去修改上面的类,但是呢如果遇到别的节假日,优惠活动又不一样,这样一直改动代码是非常不科学的,而且上面的实现也是面对实现编程,而不是面向接口编程,下面改动下代码:

定义一个卖商品促销优惠的接口:

public interface SellOrder {
void sellOrder();
}

满减实现:

public class Discounts1 implements SellOrder {
@Override
public void sellOrder() {
System.out.println("满500减100");
}
}

打折实现:

public class Discounts2 implements SellOrder {
@Override
public void sellOrder() {
System.out.println("满3件打7折");
}
}

修改后的Order类:

public class Order {
public void sellOrder(SellOrder order) {
order.sellOrder();
}
}

测试类:

public static void main(String[] args) {
Order order = new Order();
order.sellOrder(new Discounts1());
order.sellOrder(new Discounts2());
}

上面一种方式是通过方法注入的,还有构造器注入和setter注入

构造器注入:

修改Order类:

public class Order {

    public SellOrder sellOrder;

    public Order(SellOrder sellOrder) {
this.sellOrder = sellOrder;
} public void sellOrder() {
this.sellOrder.sellOrder();
}
}
 public static void main(String[] args) {
Order order1 = new Order(new Discounts1());
order1.sellOrder();
Order order2 = new Order(new Discounts2());
order2.sellOrder();
}

可以看到,构造器注入,每次都创建一个新的实例,如果SellOrder是单例的话,就需要通过setter注入

setter注入:

修改Order类:

public class Order {

    public SellOrder sellOrder;

    public void setSellOrder(SellOrder sellOrder) {
this.sellOrder = sellOrder;
} public void sellOrder() {
this.sellOrder.sellOrder();
}
}
public static void main(String[] args) {
Order order = new Order();
order.setSellOrder(new Discounts1());
order.sellOrder();
order.setSellOrder(new Discounts2());
order.sellOrder();
}

类图:

接口隔离原则 ISP

不该强迫客户程序依赖不需要使用的方法,一个接口只提供对外的功能,不是把所有功能都封装进去,减少依赖范围

优点:

1:整洁

2:系统的可维护性

下面通过一个例子说明,比如狗和鸟都需要吃东西,那么我们需要定义一个接口,然后狗和鸟分别去实现



先定义一个接口Animal:

public interface Animal {
void eat();
}

两个实现类:

public class Dog implements Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
public class Bird implements Animal {
@Override
public void eat() {
System.out.println("鸟吃虫子");
}
}

下面要求了,需要实现狗去游泳,那么我们直接在Animal接口中增加一个接口:

public interface Animal {
void eat(); void swimming();
}
    @Override
public void eat() {
System.out.println("狗吃肉");
} @Override
public void swimming() {
System.out.println("狗在游泳");
}
}
public class Bird implements Animal {
@Override
public void eat() {
System.out.println("鸟吃虫子");
} @Override
public void swimming() { }
}

这时发现了,鸟不会游泳,它的实现类里面这个方法是空的,这样就违背了接口隔离原则了,下面进行改造

定义两个接口,一个吃东西的接口和一个游泳的接口:

public interface AnimalEat {
void eat();
}
public interface AnimalSwimming {
void swimming();
}

狗的实现类:

public class Dog implements AnimalEat,AnimalSwimming {
@Override
public void eat() {
System.out.println("狗吃肉");
} @Override
public void swimming() {
System.out.println("狗在游泳");
}
}

鸟的实现类:

public class Bird implements AnimalEat {
@Override
public void eat() {
System.out.println("鸟吃虫子");
}
}

这样,鸟的实现类中就可以不用去依赖它不需要的接口了

类图:

组合复用原则 CRP

尽量使用组合,而不是继承来达到复用的目的,继承强耦合,组合低耦合,组合还能运行时动态替换

组合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的

另一种表述:要尽量使用组合,尽量不要使用继承

好处:

1:新对象存取已有对象的唯一方法是通过已有对象的接口

2:这种复用是黑箱复用,因为已有对象的内部细节是新对象所看不见的

3:支持包装

4:所需要的依赖较少

5:每一个新的类可以将焦点集中在一个任务上

6:可以在运行时动态进行,新对象可以动态的引用与已有对象类型相同的对象



人在生活中有很多种角色,在家里是子女,在学校是学生,在公司是职员,那么如果使用继承,直接继承职员类,那么我就不能再是子女或者学生了,这显然不合理,那么通过组合原则,就可以一个人拥有多种角色

顶级接口:

public interface People {
void hashRole();
}

学生、职员、子女实现类:

public class Student implements People {
@Override
public void hashRole() {
System.out.println("我是一名学生");
}
}
public class Clerk implements People {
@Override
public void hashRole() {
System.out.println("我是一个公司职员");
}
}
public class Children implements People {
@Override
public void hashRole() {
System.out.println("我是父母的儿子或女儿");
}
}

我具有的角色类:

public class My {

    private Student student;

    private Clerk clerk;

    private Children children;

    public void setMy(Student student, Clerk clerk, Children children) {
this.student = student;
this.clerk = clerk;
this.children = children; } public void myRole() {
System.out.println("我的角色:");
this.student.hashRole();
this.clerk.hashRole();
this.children.hashRole();
}
}

测试类:

public class Test {
public static void main(String[] args) {
My my = new My();
my.setMy(new Student(), new Clerk(), new Children());
my.myRole();
}
}

类图:

迪米特法则 LOD

一个对象应当对其他对象有尽可能少的了解,不和陌生人说话,降低各个对象之间的耦合,提高系统的可维护性

各种表述:

1:只与你直接的朋友们通信

2:不要和陌生人说话

3:每一个软件单位对其他软件单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位

狭义的迪米特法则:

如果两个类不需要彼此之间直接通信,那么这两个类就不应该发生直接的相互作用,如果一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用

什么样的才可以称为朋友:

1:当前对象本身(this)

2:以参量的形式传入到当前对象方法中的对象

3:当前对象的实例变量直接引用的对象

4:当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友

5:当前对象所创建的对象

任何一个对象如果满足上面的条件之一,就可以称为朋友,否则就是陌生人

迪米特法则主要的用意是控制信息的过载,在做系统设计时需要注意一下几点:

1:在类的划分上,应当创建有弱耦合的类,类之间的耦合越弱,越有利于复用,一个弱耦合的类被修改了,不会波及到其有关联的类

2:每一个类都应该降低成员的访问权限

3:一个类应该设计成不变类

4:在对其他类的引用上,一个对象对其对象的引用应该降到最低



举个例子,小明和小红是朋友,小刚只认识小红,现在小明有事情需要小刚帮忙去做,那么小明不应该直接去找小刚,而是找到小红,让小红找到小刚,然后去完成事情

public class XiaoMing {
public void xiaoMing(Xiaohong xiaohong) {
System.out.println("我是小明,找:" + xiaohong);
xiaohong.xiaohong();
}
}
public class XiaoHong {
public void xiaohong() {
System.out.println("我是小红,你要找我吗");
XiaoGang xiaoGang = new XiaoGang();
xiaoGang.xiaoGang();
}
}
public class XiaoGang {
public void xiaoGang() {
System.out.println("我是小刚。小红在找我");
}
}

测试类:

public class Test {
public static void main(String[] args) {
XiaoHong xiaohong = new XiaoHong();
XiaoMing xiaoMing = new XiaoMing();
xiaoMing.xiaoMing(xiaohong);
}
}

类图:



到此,7个设计原则已经表述完了,学习设计原则是为了写出更好更优秀的代码,但是也不需要刻意的去准守

Java面向对象7大设计原则的更多相关文章

  1. Java设计模式GOF之6大设计原则

    Java设计模式GOF之6大设计原则原则 1.开闭原则(Open Close Principle) 一个软件实体如类.模块和函数应该对扩展开放,对修改关闭. 开闭原则是面向对象的可复用设计的第一块基石 ...

  2. java设计6大设计原则

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  3. JAVA面向对象编程课程设计——网络版单机斗地主

    一.团队介绍 成员姓名 任务分配 成员课程设计博客链接 兰泽祥(组长) 数据库,斗地主规则的实现,人机自动出牌的算法,实体类的设计 JAVA面向对象编程课程设计--web版斗地主 吴修恩 JSP界面的 ...

  4. JAVA面向对象编程课程设计——web版斗地主

    一.团队课程设计博客链接 JAVA面向对象编程课程设计--网络版单机斗地主 二.个人负责模块或任务说明 实体类的设计 斗地主规则的实现 人机自动出牌的算法 实现数据库的DAO模式 三.自己的代码提交记 ...

  5. 什么是好的产品——Diet Rams的十大设计原则

    博朗(BRAUN)的首席设计师Diet Rams的十大设计原则 第一条,好的产品是有创意的,它必须是一个创新的东西: 第二条,好的产品是有用的,一定要对人有用: 第三条,好的产品是优美的,它必须有美感 ...

  6. Python6大设计原则

    内容总览 六大设计原则都有哪些 一.单一职责原则 二.里氏替换原则 三.依赖倒置原则 四.接口隔离原则 五.迪米特法则 六.开放封闭原则 内容详解 一.单一职责原则 单一职责原则:英文名称是Singl ...

  7. Java 设计模式 和七大设计原则

    创建型模式 抽象工厂模式(Abstract factory pattern): 提供一个接口, 用于创建相关或依赖对象的家族, 而不需要指定具体类. 生成器模式(Builder pattern): 使 ...

  8. 面向对象程序的设计原则--Head First 设计模式笔记

    一.找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起. 把会变化的部分取出并“封装”起来,好让其他部分不会受到影响.这样,代码变化引起的不经意后果变少,系统变得更有弹性 ...

  9. JAVA面向对象编程课程设计——项目部署

    目录 一.Java环境的安装 1.下载 2.安装 3.配置环境变量 二.Tomcat的安装 1.下载 2.安装 3.启动Tomcat(默认已经安装好java环境,如果未安装java会报错.) 三.My ...

随机推荐

  1. HTTP 协议中的并发限制及队首阻塞问题

    本文转载自HTTP 协议中的并发限制及队首阻塞问题 串行连接 HTTP/0.9 和早期的 HTTP/1.0 协议对 HTTP 请求处理是串行化的.假如一个页面包含 3 个样式文件,同属于一个协议.域名 ...

  2. Elasticsearch 及其套件的安装上手

    前言 本文主要讲解Elasticsearch及其套件Kibana.Logstash的安装及启动,还讲解如何导入数据用于后续的实验. 说明:Elasticsearch是基于Java开发的,所以如果是下载 ...

  3. 开源OA办公平台搭建教程:基于nginx的快速集群部署——端口分发

    主机信息 主机1:172.16.98.8(linux) 主机2:172.16.98.9(linux) 集群需求 172.16.98.8:WEB服务器,应用服务器,文件存储服务器,中心服务器 172.1 ...

  4. 链接服务器sql语句

     EXEC  sp_addlinkedserver      @server='sha',--被访问的服务器别名       @srvproduct='',      @provider='SQLOL ...

  5. Linux关机指令详解

    Linux关机指令 在linux领域内大多用在服务器上,很少遇到关机的操作.毕竟服务器上跑一个服务是永无止境的,除非特殊情况下,不得已才会关机. 正确的关机流程为:sync > shutdown ...

  6. 关于《Android编程权威指南》的MockWalker在模拟器中无法运行的解决方法

    1.打开模拟器中的Dev Settings应用. 2.选中Allow mock locations选项. 之后应该就能正常运行了.

  7. SpringCloud里面切换数据源无效的问题

    问题描述: 调用链:controller1的接口A->service1的方法A->service2的方法B 方法A开启了事务,且指定了数据库A的数据源 方法B也开启了事务,使用了默认的事务 ...

  8. 选择 FreeBSD 而不是 Linux 的技术性原因2

    ZFSZFS 文件系统是 FreeBSD 上的一等公民.这不仅意味着可以在 ZFS 上安装根目录,安装程序也支持这一点,而且还意味着很多基础系统工具都已经紧密地集成或构建了对 ZFS 的支持.在 Fr ...

  9. 用实战玩转pandas数据分析(一)——用户消费行为分析(python)

      CD商品订单数据的分析总结.根据订单数据(用户的消费记录),从时间维度和用户维度,分析该网站用户的消费行为.通过此案例,总结订单数据的一些共性,能通过用户的消费记录挖掘出对业务有用的信息.对其他产 ...

  10. windows程序员开发linux程序的头一个月

    开发环境选择 vim,vscode,qt,visual studio都可以做linux c++开发,但是作为windows程序员,最熟悉的还是visual stuio,加上visual studio ...