介绍
内容 | 链接 |
---|---|
设计模式简介 | https://blog.csdn.net/m0_54485604/article/details/113309133 |
UML 类图介绍
内容 | 链接 |
---|---|
UML类图 | https://blog.csdn.net/m0_54485604/article/details/113243818 |
六大设计原则
内容 | 链接 |
---|---|
开闭原则 | https://blog.csdn.net/m0_54485604/article/details/113502478 |
接口隔离 | https://blog.csdn.net/m0_54485604/article/details/113565015 |
里氏替换 | https://blog.csdn.net/m0_54485604/article/details/113760408 |
单一职责 | https://blog.csdn.net/m0_54485604/article/details/113498517 |
依赖倒转(倒置) | https://blog.csdn.net/m0_54485604/article/details/113756740 |
迪米特法则 | https://blog.csdn.net/m0_54485604/article/details/113760176 |
创建型设计模式
内容 | 链接 |
---|---|
单例模式 | 无 |
工厂模式 | 无 |
抽象工厂模式 | 无 |
建造者模式 | 无 |
原型模式 | 无 |
结构型设计模式
内容 | 链接 |
---|---|
适配器模式 | 无 |
桥接模式 | 无 |
装饰模式 | 无 |
组合模式 | 无 |
外观模式 | 无 |
享元模式 | 无 |
代理模式 | 无 |
行为型设计模式
内容 | 链接 |
---|---|
模版方法模式 | 无 |
命令模式 | 无 |
访问者模式 | 无 |
迭代器模式 | 无 |
观察者模式 | 无 |
中介者模式 | 无 |
备忘录模式 | 无 |
解释器模式(Interpreter 模式) | 无 |
状态模式 | 无 |
策略模式 | 无 |
职责链模式(责任链模式) | 无 |
工厂的作用就是可以我们可以利于工厂类来根据需要地去创建对应的对象,且这几个对象一般是一个抽象类/父类/接口的子类,这个工厂类一般需要一个成员变量,这个变量是那几个实例的父类/抽象类/接口类型的变量,然后有一个方法可以根据你的需求生成对应的对象且给赋值给那个变量,然后这个方法return那个成员变量,主要用了多态。可以看下面这个例子:
虽然使用工厂类是为了实例化对象,但是还有一点就是,可以让类符合的单一职责原则。但是类的划分不能太细,比如商店三折、五折、六折、满300减30,这里你要是划分为四个类就不太合适,应该划分为两个类,一个打折类,一个满减类。打折类只要你构造这个类的时候,传一个折扣力度就行了,就能体现三折、五折、六折的效果。然后你这两个类(打折类和满减类)继承一个父类,这个父类叫优惠类。优惠类里面只有一个抽象方法,叫优惠,然后你有一个工厂类,工厂类就是为了给打折类和满减类创建对象的,根据那个创建对象的方法的参数确定创建哪个对象。然后具体测试的时候,比如有一个test类用来测试,这个test类用表面是父类的一个引用去调用那个父类的抽象方法,执行的时候会去执行具体的那个实例的方法的,这就是工厂的使用。为什么这里的类只划分为两个类呢?因为类不是分得越多越好的,我们把某些东西分出来作为一个类就是为了把那些东西共同的特征抽象出来作为一个分类,这才是我们类划分的标准。
就是把那些算法都封装起来,放到一个接口下面,什么是算法呢?就是比如你打折的计算规则,打五折是把原价乘0.5,打三折乘0.3,满300减30就是满多少个300就减多少个30就行了,这些都是算法,简单点说就是处理规则嘛。我们比如设计一个优惠这个接口,接口被两个子类实现,一个是打折类,一个是满减类。接口里面写的是那个优惠的抽象方法,子类都实现他。这就像是工厂模式下的那个抽象类和子类一样。这个context类里面有一个成员变量,变量数据类型是那个接口类型,然后写一个context类,这个context类里面有一个方法,这个方法接受外面参数拿到接口的实现类然后把对象赋值给context的成员变量,这个方法的参数是那个接口,这样你就可以传具体子类进来了,然后给到context的成员变量,然后,这个context对象有另一个方法,就比如叫获取优惠结果方法,这个方法的方法体是执行那个接口类型的成员变量的抽象方法,因为你成员变量的具体类型是你传进来的类型,所以这里执行的时候会执行具体类型的方法(我们把那些打折的算法、满减的算法都写在那个具体类的实现方法里面)。然后你具体使用的时候,比如你在test类里面使用的时候,就这样写,你就在test类造一个接口的实现类对象,然后你new一个context类对象,然后传给context接受参数的方法里,然后test类里面用这个context类对象执行那个获取优惠这个方法,这样就是策略模式了。策略模式里那个接口下实现类里面的算法都是一个做一类事情的算法,比如都是做优惠这个事情的不同算法,只是执行优惠力度不同规则不同罢了。这些算法他们是可以相互替代的,比如我们今天这个商场开展全部商品打5折的优惠策略,明天这个商场开展满300减30的优惠策略,那么我们只要在test类里面把那个传给context接收参数的那个方法一个满300减30的类的对象就行了,你原来那个context接收参数的那个方法里面是传一个打5折的类对象嘛,现在你传了一个满300减30的对象。就这样我们就达到了把都是做优惠的这个事情,从一个打五折到满300减30的转变了,只是换了一个优惠的算法罢了,但是都是做一个事情,就是优惠。用策略模式有一个好处就是,你要换一个算法的时候,只要换一个new的对象就行了,其他都不用变。所以只要看到在不同时间用不同的业务规则的地方都比较好用策略模式。
上面是标准的使用,下面看看实际使用:
就是我们把上面的策略模式的创建实例这个过程也放到context类里面就是工厂模式和策略模式结合了,我们把生成实例的过程放到context类里面就不用传一个接口实现类对象到context那个方法里面了,直接按你需求生成实例的时候给成员变量赋值,然后然后使用的时候直接调用那个获取优惠这个方法就行了。
结合后的代码如下:
代理模式的结构图:
标准代码:
他们的结构重点就是:子类和代理类都是一个接口的子类,相当于他们是兄弟,然后你具体操作的时候new的是代理对象,且执行的是代理对象的方法,然后你代理对象的方法里是执行某个真实对象的某个方法的,所以这样你使用类就会间接地去执行真实对象的某个方法。
具体的例子如下:
注意装饰者模式和建造者模式是不同的,建造者模式要求建造的过程是稳定的,但是装饰者模式的建造过程不是稳定的。就比如你要装饰一个人,可以先把人穿衬衫,再穿裤子,再头外套。你先给人穿裤子,再穿衬衫,再穿外套也行。
标准的代码:
装饰模式的变形:
代码实现:
分析:你pqx.decorate(xc)就是相当于让那个xc对象成为“pqx对象内的component的真实的对象”,这样执行pqx.show的时候就会先打印“破球鞋”再去执行xc的show,即打印“装扮的小菜”。然后我们kk.decorate(pqx),即让pqx成为kk内component的真实对象,这样执行kk.show的时候就会先打印“垮裤”,再去执行pqx.show(),pqx.show()前面讲过了,就是打印“破球鞋”再打印“装扮的小菜”。然后我们dtx.decorate(kk),就是让kk成为dtx的component的真实对象,然后执行dtx.show()的话,就是相当于先打印“大T恤”,然后执行kk.show(),就是打印“垮裤 破球鞋 装扮的小菜”。
其实它就像是自己构造了一个虚拟的父子链,装饰模式的前提条件是:你要构成那个结构图,即你具体服饰类要继承一个抽象类,这个抽象类里面有一个成员变量,一个给这个成员变量赋值的方法,还有一个要被重写的方法,你具体服饰类去继承这个抽象类,那么你具体服饰类也会有建立给那个成员变量赋值的方法了,且具体服饰类也相当于把抽象类的成员变量据为己有了。然后你那个被继承的方法里面会有调用父类的方法。好,前提条件建好了,下面开始构造虚拟父子链,给成员变量赋值的过程就是建立虚拟父子链的过程。其实上面的装饰类里还有一个细节,就是,装饰抽象类里面的decorate方法的形参是person,这样小菜这个对象就能被当作参数传进去了,才能让小菜有机会成为某个具体服饰类的虚拟父类
下面做一个练习加强一下装饰者模式。
题目:某咖啡店售卖咖啡时,可以根据顾客的要求在其中加入各种配料,咖啡店会根据所加入的配料来计算费用。咖啡店所供应的咖啡及配料的种类和价格如下表所示。
现采用装饰器(Decorator)模式来实现计算费用的功能,得到如图所示的类图。
代码如下:
public (1) class Beverage {//饮料类
String description = "Unknown Beverage";
public (2) {
return description;
}
public (3);
}
public abstract class CondimentDecorator extends Beverage {//配料
(4);
}
public class DarkRoast extends Beverage {//深度烘焙咖啡
private final int DARKROAST_PRICE = 20;
public DarkRoast() {
description = "DarkRoast";
}
public int cost() {
return DARKROAST_PRICE;
}
}
public class Espresso extends Beverage {//蒸馏咖啡
private final int ESPRESSO_PRICE = 25;
public Espresso() {
description = "Espresso";
}
public int cost() {
return ESPRESSO_PRICE;
}
}
public class Mocha extends CondimentDecorator {//摩卡
private final int MOCHA_PRICE = 10;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ",Mocha";
}
public int cost() {
return MOCHA_PRICE + beverage.cost();
}
}
public class Whip extends CondimentDecorator {//奶泡
private final int WHIP_PRICE = 8;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", whip";
}
public int cost() {
return WHIP_PRICE + beverage.cost();
}
}
public class Test {
public static void main(String[] args) {
Beverage beverage = new DarkRoast();
beverage = new Mocha( (5) );
beverage = new Whip( (6) );
System.out.println(beverage.getDescription() + " ¥" + beverage.cost());
}
}
结果:
答案:
职责链模式的结构图如下:
职责链模式的标准代码如下:
他的主要思想就是通过建动态建链,如下图
然后你用底层的这个类的方法去处理一个请求,然后会把这个请求进行判断,要是本类能处理,就本类处理,要是不能处理这个请求就给这个类的上一级来处理。
他的执行过程是:要么自己来处理,要么就给上一级来处理。处理过程是这样的。相当于里面的successor.handleRequest(request)就是上一层的对象的handleRequest(request)方法里面的语句,successor.handleRequest(request)就是)就是上一层的对象的handleRequest(request)方法最小化。
职责链模式看下面这个题可以加深理解:
某发票(lnvoice)由抬头(Head)部分、正文部分和脚注(Foot)部分构成。现采用装饰(Decorator)模式实现打印发票的功能,得到如图类图:
代码:
答案:(这里的ticket!=后面少了一个null而已)
我们先看一下简单工厂的代码:
可以看到这里简单工厂的的这个工厂类里面存在switch-case。然后我们要修改的添加某个功能的话,就要添加一个case语句,就破坏了开闭原则。然后我们采用工厂方法模式来改进这里的简单工厂的这个问题。
下面是工厂方法:(工厂方法添加功能是扩展,比如我们要添加M的N次这个功能,我们就可以在这个运算类的子类添加一个M的N次方类,然后在工厂类下面添加一个M的N次方工厂这个具体工厂类,这样就他们完成添加新的功能了,他们就可以不用修改方法里面的代码了,我们只要扩展一个类就行。然后你只要把客户端的那个代码new addtion()改为新添加的类就行了。)
其实工厂方法就是把工厂类的简单工厂改造为了一个工厂下面很多小工厂。之前是一个简单工厂类负责去new具体功能的类(如加法类),现在是你具体体工厂类去new那些具体功能类(如加法类)。
我们先来看个例子,工厂方法访问数据库:
如果这里让具体工厂再添加一个功能,变为了抽象工厂类了。看下面代码:(这里设置的这几个接口,IFactory、IUser、IDepartment这些接口都是为了让客户端代码认识的,这样客户端代码就不用去认识具体类了,比如他就不用认识SqlserverFactory、SqlserverUser类。这样到时候你想要改需求的时候,比如你要把Sqlserver改为Access的时候,只要把new的地方改了,就行了(这里你看看客户端代码)。)(还有一点需要注意的,就是你这里为什么新添加一个部门表就要新添加一个类呢?这样那个SqlserverDepartment这个类下面就只有两个方法,一个插入一个查询方法。答:这样的话,职责更加单一。这个SqlserverDepartment类里面只写Sqlserver的Department有关的方法。如果和下面这个结构图一样,你首先职责分开地不够明确,然后你要添加一个功能就得在这个具体sql类里面添加方法。虽然也没有破坏开闭原则,但是那个一个类里面的东西比较杂。不好。)
下面是具体代码
抽象工厂模式的结构图如下:这里的AbstractProductA就是为了让客户端认识,这样的话,客户端就不用去认识ProductA 1了,降低了客户端代码的耦合性。上面的这个SqlserverUser可以理解为ProductA1,这个SqlserverDepartment是ProductB1。那个ConcreteFactory1就是SqlserverFactory。
工厂方法的结构如下:
抽象工厂的结构如下:(即抽象工厂是一个工厂可以创建多个产品对象的,但是工厂方法是一个工厂创建一个产品对象的)
因为抽象工厂要添加一个功能就要添加多个类,并且修改多个类。比如我们要添加一个项目表,就得添加IProject、SqlserverProject、AccessProject,还需要改IFactory、SqlserverFactory、AccessFactory。如果有很多类用到了new SqlserverFactory(),然后你现在换数据库了,你要把这个new SqlserverFactory()改为new AccessFactory(),就要改很多类。所以我们这样来改进:简单工厂+抽象工厂。
我们把几个工厂类都合并为了一个DataAccess类。然后你要添加某个功能,要去操作另一个表,比如要能查询和插入项目表,你就只要在这个DataAccess添加一个方法,然后添加一个IProject接口,一个SqlserverProject类、一个AccessDepartment类,就好了,这样添加类就少了。而且你要切换数据库,只要修改一下这个成员变量就行了。然后你看客户端这些代码都是纯对面向接口的,里面没有一个SqlServer或者Access的字样。所以就算有100个使用这个工厂类的其他客户端的类,现在要切换数据库了,也不用改这100个地方了,只要改一下成员变量就行了。
但是这里成员变量是硬编码,这也是一个问题,之后可以用反射+配置文件来解决。还有这里有存在很多case语句,这样我们要添加一个新的数据库,就要来修改这里的case了,破坏了开闭原则。下面我们来用反射+配置文件来解决。
简单工厂+抽象工厂+反射+配置文件的写法:
package Data;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Util {
static File file=new File("E:\\IdeaProjects\\JavaSE_code\\daier\\src\\Data\\db.properties");
public static String read(String key){
Properties prop = new Properties();
FileReader fr=null;
String db=null;
try {
fr = new FileReader(file);
prop.load(fr);
db = prop.getProperty(key);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr!=null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return db;
}
}
package Data;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
User user=new User();
Department dept=new Department();
IUser iu=DataAccess.createUser();
iu.insert(user);
iu.getUser(1);
IDepartment id=DataAccess.createDepartment();
id.insert(dept);
id.getDepartment(1);
}
}
package Data;
import java.lang.reflect.Constructor;
public class DataAccess {
public static IUser createUser(){
IUser iUser=null;
try {
String user = Util.read("user");
Class<?> carFactory = Class.forName(user);
Constructor<?> con = carFactory.getConstructor();
iUser= (IUser) con.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return iUser;
}
public static IDepartment createDepartment(){
IDepartment iDepartment=null;
try {
String department = Util.read("department");
Class<?> carFactory = Class.forName(department);
Constructor<?> con = carFactory.getConstructor();
iDepartment= (IDepartment) con.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return iDepartment;
}
}
package Data;
public interface IUser {
void insert(User user);
User getUser(int id);
}
package Data;
public class SqlServerUser implements IUser{
public void insert(User user){
System.out.println("在sql server中给usre表添加一条记录!");
}
public User getUser(int id){
System.out.println("在sql server中得到对应id的user");
return null;
}
}
package Data;
public class AccessUser implements IUser{
public void insert(User user){
System.out.println("在AccessUser中给usre表添加一条记录!");
}
public User getUser(int id){
System.out.println("在AccessUser中得到对应id的user");
return null;
}
}
package Data;
public interface IDepartment {
void insert(Department department);
User getDepartment(int id);
}
package Data;
public class SqlServerDepartment implements IDepartment{
public void insert(Department department){
System.out.println("在sql server中给department表添加一条记录!");
}
public User getDepartment(int id){
System.out.println("在sql server中得到对应id的department记录");
return null;
}
}
package Data;
public class AccessDepartment implements IDepartment{
public void insert(Department department){
System.out.println("在Access中给department表添加一条记录!");
}
public User getDepartment(int id){
System.out.println("在Access中得到对应id的department记录");
return null;
}
}
package Data;
public class User {
private int id;
private String name;
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package Data;
public class Department {
private int id;
private String depName;
public Department() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDepName() {
return depName;
}
public void setDepName(String depName) {
this.depName = depName;
}
}
user=Data.SqlServerUser
department=Data.SqlServerDepartment
依赖倒装原则介绍
高层模块不应该依赖低层模块,二者都应该依赖其抽象
抽象不应该依赖细节,细节应该依赖抽象
依赖倒转(倒置)的中心思想是面向接口编程
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
传统调用方法
package com.study;
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email {
public String getInfo(){
return "电子邮件信息: hello,world";
}
}
/**
* 1.优点:调用方式简单
* 2.存在问题:如果我们获取的对象是 微信,短信等等,则新增类,同时 Person 也要增加相应的接收方法
* 3.解决方法:引入一个抽象的接口 IReceiver, 表示接收者, 这样 Person 类与接口 IReceiver 发生依赖
* 因为 Email, WeiXin 等等属于接收的范围,他们各自实现 IReceiver 接口, 这样我们就符号依赖倒转原则
*/
class Person {
public void receive(Email email){
System.out.println(email.getInfo());
}
}
通过依赖倒转的案例
package com.study;
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new WX());
}
}
interface IReceive {
String getInfo();
}
class Email implements IReceive{
public String getInfo(){
return "电子邮件信息: hello,world";
}
}
//添加微信类
class WX implements IReceive{
public String getInfo(){
return "微信信息: hello,world";
}
}
class Person {
public void receive(IReceive iReceive){
System.out.println(iReceive.getInfo());
}
}
依赖关系传递的三种方式(接口传递,构造方法传递,setter 方式传递)
接口传递:
package com.study;
public class DependencyPass {
public static void main(String[] args) {
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(new ChangHong());
}
}
//ITV 接口
interface ITV {
void play();
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
interface IOpenAndClose {
void open(ITV tv);
}
class OpenAndClose implements IOpenAndClose{
public void open(ITV tv){
tv.play();
}
}
构造方法传递
package com.study;
public class DependencyPass {
public static void main(String[] args) {
OpenAndClose openAndClose = new OpenAndClose(new ChangHong());
openAndClose.open();
}
}
//ITV 接口
interface ITV {
void play();
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
interface IOpenAndClose {
void open();
}
class OpenAndClose implements IOpenAndClose{
//成员
public ITV itv;
//构造器
public OpenAndClose(ITV itv) {
this.itv = itv;
}
public void open(){
itv.play();
}
}
setter方式传递
package com.study;
public class DependencyPass {
public static void main(String[] args) {
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.setITV(new ChangHong());
openAndClose.open();
}
}
//ITV 接口
interface ITV {
void play();
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
interface IOpenAndClose {
void open();
void setITV(ITV itv);
}
class OpenAndClose implements IOpenAndClose{
private ITV itv;
@Override
public void setITV(ITV itv) {
this.itv = itv;
}
public void open(){
itv.play();
}
}
注意点:
声明类型尽量是抽象类或接口
, 这样我们的变量引用和实际对象间,就存在一个缓冲层
,利于程序扩展和优化里氏替换原则
介绍
里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的。
如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖 来解决问题。
存在问题的案例
package com.study;
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3=" + a.func1(11, 3));
System.out.println("1-8=" + a.func1(1, 8));
System.out.println("-----------------------");
B b = new B();
//本意是求出 11-3 和 1-8 但是由于重写改变了之前的职责
System.out.println("11-3=" + b.func1(11, 3));
System.out.println("1-8=" + b.func1(1, 8));
System.out.println("11+3+9=" + b.func2(11, 3));
}
}
//类 A
class A {
// 返回两个数的差
public int func1(int num1, int num2){
return num1 - num2;
}
}
class B extends A {
//重写了 A 类的方法, 可能是无意识
public int func1(int a, int b){
return a + b;
}
public int func2(int a, int b){
return func1(a, b) + 9;
}
}
里氏替换原则解决
问题说明:
上面的代码出现了意外情况,原因就是类 B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
通用做法:
原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替
解决方案:
package com.study;
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3=" + a.func1(11, 3));
System.out.println("1-8=" + a.func1(1, 8));
B b = new B();
//因为 B 类不再继承 A 类,因此调用者,不会再 func1 是求减法 ,调用会很明确
System.out.println("11+3=" + b.func1(11, 3));
System.out.println("1+8=" + b.func1(1, 8));
System.out.println("11+3+9=" + b.func2(11, 3));
//使用组合仍然可以使用到 A 类相关方法
System.out.println("11-3=" + b.func3(11, 3));
}
}
//创建一个更加基础的基类
class Base {
//把更加基础的方法和成员写到 Base 类
}
//类 A
class A extends Base {
// 返回两个数的差
public int func1(int num1, int num2){
return num1 - num2;
}
}
class B extends Base {
//如果 B 需要使用 A 类的方法,使用组合关系
private A a = new A();
//重写了 A 类的方法, 可能是无意识
public int func1(int a, int b){
return a + b;
}
public int func2(int a, int b){
return func1(a, b) + 9;
}
public int func3(int a, int b){
return this.a.func1(a, b);
}
}
介绍
开闭原则(Open Closed Principle)是编程中最基础、最重要设计原则
一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
违反开闭原则示例
package com.study;
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 = 2;
}
}
总结上面代码的优缺点:
使用开闭原则改进
思路:
把创建Shape 类做成抽象类
,并提供一个抽象的 draw 方法
,让子类去实现
即可,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可,使用方的代码就不需要修改了
, 从而满足了开闭原则。
package com.study;
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) {
s.draw();
}
}
//Shape 类,基类
abstract class Shape {
abstract void draw();
}
class Rectangle extends Shape {
@Override
void draw() {
System.out.println(" 绘制矩形 ");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println(" 绘制圆形 ");
}
}
//新增画三角形
class Triangle extends Shape {
@Override
void draw() {
System.out.println(" 绘制三角形 ");
}
}
介绍
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
例如:类 A 负责两个不同职责:职责 1,职责 2。当职责 1 需求变更而改变 A 时,可能造成职责 2 执行错误,所以需要将类 A 的粒度分解为 A1,A2
单一职责的案例说明
方式一:
package com.study;
/**
* 单一职责
* @author xiaosong
* @version 1.0
* @since 2021/1/28
*/
public class SingleResponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
/**
* 1. 此类的 run 方法中,违反了单一职责原则
* 2. 解决的方案:根据交通工具运行方法不同,分解成不同类
*/
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路上运行....");
}
}
方式二:
package com.study;
/**
* 单一职责
* @author xiaosong
* @version 1.0
* @since 2021/1/28
*/
public class SingleResponsibility {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("摩托车");
roadVehicle.run("汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
}
}
/**
* 1. 遵守单一职责原则
* 2. 但是这样做的改动很大,将类分解,同时修改客户端
* 3. 改进:直接修改 Vehicle 类,改动的代码会比较少=>方案 3
*/
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "公路运行");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "天空运行");
}
}
方式三:
package com.study;
/**
* 单一职责
* @author xiaosong
* @version 1.0
* @since 2021/1/28
*/
public class SingleResponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.runRoad("摩托车");
vehicle.runRoad("汽车");
vehicle.runAir("飞机");
}
}
/**
* 1. 这种修改方法没有对原来的类做大的修改,只是增加方法
* 2. 这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责
*/
class Vehicle {
public void runRoad(String vehicle) {
System.out.println(vehicle + " 在公路上运行....");
}
public void runAir(String vehicle) {
System.out.println(vehicle + " 在天空上运行....");
}
}
单一职责原则注意事项和细节
介绍
一个对象应该对其他对象保持最少的了解
类与类关系越密切,耦合度越大
迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
迪米特法则还有个更简单的定义:只与直接的朋友通信
直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
违反迪米特法则示例
需求:
有一个学校,下属有各个学院和总部,现要求打印出学校总部员工 ID 和学院员工的 id
package com.study;
import java.util.ArrayList;
import java.util.List;
public class Demeter {
public static void main(String[] args) {
//创建了一个 SchoolManager 对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工 id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//学院的员工类
class CollegeEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理学院员工的管理类
class CollegeManager {
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<>();
//增加 10 个员工到 list
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工 id= " + i);
list.add(emp);
}
return list;
}
}
/*
1. 分析 SchoolManager 类的直接朋友类有 Employee、CollegeManager
2. CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
*/
//学校管理类
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
//增加 5 个员工到 list
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学校总部员工 id= " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
/*
问题分析:
1. 这里的 CollegeEmployee 不是 SchoolManager 的直接朋友
2. CollegeEmployee 是以局部变量方式出现在 SchoolManager
3. 违反了 迪米特法则
*/
//获取到学院员工
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println("------------学院员工------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------学校总部员工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
使用迪米特法则进行改进
改进:
SchoolManager 中,CollegeEmployee 类并不是 SchoolManager 类的直接朋友
package com.study;
import java.util.ArrayList;
import java.util.List;
public class Demeter {
public static void main(String[] args) {
//创建了一个 SchoolManager 对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工 id 和 学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//学院的员工类
class CollegeEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理学院员工的管理类
class CollegeManager {
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<>();
//增加 10 个员工到 list
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工 id= " + i);
list.add(emp);
}
return list;
}
public void printCollege(){
//获取到学院员工
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("------------学院员工------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//学校管理类
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
//增加 5 个员工到 list
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学校总部员工 id= " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
//将输出学院的员工方法,封装到 CollegeManager
sub.printCollege();
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------学校总部员工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
迪米特法则注意事项和细节
降低类之间的耦合
只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系
介绍
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
传统调用方法
package com.study;
public class Segregation {
public static void main(String[] args) {
A a = new A();
a.depend1(new B()); // A 类通过接口去依赖 B 类
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D()); // C 类通过接口去依赖(使用)D 类
c.depend4(new D());
c.depend5(new D());
}
}
//A 类通过接口 Interface1 依赖(使用) B 类,但是只会用到 1,2,3 方法
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface1 i) {
i.operation2();
}
public void depend3(Interface1 i) {
i.operation3();
}
}
//C 类通过接口 Interface1 依赖(使用) D 类,但是只会用到 1,4,5 方法
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface1 i) {
i.operation4();
}
public void depend5(Interface1 i) {
i.operation5();
}
}
//接口
interface Interface1 {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1 {
public void operation1() {
System.out.println("B 实现了 operation1");
}
public void operation2() {
System.out.println("B 实现了 operation2");
}
public void operation3() {
System.out.println("B 实现了 operation3");
}
public void operation4() {
System.out.println("B 实现了 operation4");
}
public void operation5() {
System.out.println("B 实现了 operation5");
}
}
class D implements Interface1 {
public void operation1() {
System.out.println("B 实现了 operation1");
}
public void operation2() {
System.out.println("B 实现了 operation2");
}
public void operation3() {
System.out.println("B 实现了 operation3");
}
public void operation4() {
System.out.println("B 实现了 operation4");
}
public void operation5() {
System.out.println("B 实现了 operation5");
}
}
使用接口隔离改进后的方法
package com.study;
public class Segregation {
public static void main(String[] args) {
A a = new A();
a.depend1(new B()); // A 类通过接口去依赖 B 类
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D()); // C 类通过接口去依赖(使用)D 类
c.depend4(new D());
c.depend5(new D());
}
}
//A 类通过接口 Interface1 依赖(使用) B 类,但是只会用到 1,2,3 方法
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i) {
i.operation2();
}
public void depend3(Interface2 i) {
i.operation3();
}
}
//C 类通过接口 Interface1 依赖(使用) D 类,但是只会用到 1,4,5 方法
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface3 i) {
i.operation4();
}
public void depend5(Interface3 i) {
i.operation5();
}
}
//接口
interface Interface1 {
void operation1();
}
interface Interface2 {
void operation2();
void operation3();
}
interface Interface3 {
void operation4();
void operation5();
}
class B implements Interface1,Interface2 {
public void operation1() {
System.out.println("B 实现了 operation1");
}
public void operation2() {
System.out.println("B 实现了 operation2");
}
public void operation3() {
System.out.println("B 实现了 operation3");
}
}
class D implements Interface1,Interface3 {
public void operation1() {
System.out.println("B 实现了 operation1");
}
public void operation4() {
System.out.println("B 实现了 operation4");
}
public void operation5() {
System.out.println("B 实现了 operation5");
}
}