您现在的位置是:首页 >技术交流 >设计模式-七大原则网站首页技术交流
设计模式-七大原则
设计模式-七大原则
简述
七大原则
单一职责原则
接口隔离原则
依赖倒转原则
里氏替换原则
开闭原则
迪米特法则
合成复原原则
七大原则和设计模式的关系是什么
无规矩不成方圆。七大原则可以理解为设计模式的底层要素,设计模式是必须要遵守这七大原则的。
七大设计模式
单一职责原则(Single responsibility principle)
单一职责原则就是说一个类只能负责一块职责,要是有不用的职责则要拆分成多个类来实现。例如我们在平常开发中,一个表就有一个DAO类,这个DAO类里面写关于这个表的SQL语句,不同的DAO类负责不同的表。
遵守这个原则可以充分降低类的复杂度,提高类的可读性和可维护性。
应用实例
1、我们写这样一个代码:
public class Demo {
public static void main(String[] args) {
Transportation transportation = new Transportation();
transportation.run("汽车");
}
}
class Transportation {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上行驶");
}
}
运行结果:
2、我们发现这里只能写在陆地上运行的交通工具:
3、我们来改造一下这个代码,并且遵守单一职责原则:
public class Demo {
public static void main(String[] args) {
WayTransportation wayTransportation = new WayTransportation();
wayTransportation.run("汽车");
SkyTransportation skyTransportation = new SkyTransportation();
skyTransportation.run("飞机");
WaterTransportation waterTransportation = new WaterTransportation();
waterTransportation.run("轮船");
}
}
class WayTransportation {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上行驶");
}
}
class SkyTransportation {
public void run(String vehicle) {
System.out.println(vehicle + "在天空上飞行");
}
}
class WaterTransportation {
public void run(String vehicle) {
System.out.println(vehicle + "在水中上行驶");
}
}
运行结果:
这就是单一职责原则的体现,一个类只负责一个职责,公路上行驶的交通工具就通过WayTransportation实现,天空上飞行的就通过SkyTransportation实现,水中行驶的就通过WaterTransportation实现。
注意事项和细节
通常代码都应该遵守单一职责原则,只有当类的逻辑足够简单,才能违背单一职责原则,可以在方法层面保持单一职责原则。
public class Demo {
public static void main(String[] args) {
Transportation ransportation = new Transportation();
ransportation.runWay("汽车");
ransportation.runSky("飞机");
ransportation.runWater("轮船");
}
}
class Transportation {
public void runWay(String vehicle) {
System.out.println(vehicle + "在公路上行驶");
}
public void runSky(String vehicle) {
System.out.println(vehicle + "在天空上飞行");
}
public void runWater(String vehicle) {
System.out.println(vehicle + "在水中上行驶");
}
}
接口隔离原则(Interface Segregation Principle)
官方:客户端不应该依赖它不需要的接口类,类之间的依赖关系应该建立在最小接口上。
大白话:一个实现接口的类中,如果类中有用不着的方法,那就需要将接口拆分。拆分成最小粒度的接口,即类不会存在不需要的方法。
应用实例
我们先看一下这张UML类图
我们来写一下这样的代码:
public interface Interface1 {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
类B的代码
public class B implements Interface1{
@Override
public void operation1() {
}
@Override
public void operation2() {
}
@Override
public void operation3() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
}
类D的代码
public class D implements Interface1{
@Override
public void operation1() {
}
@Override
public void operation2() {
}
@Override
public void operation3() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
}
类A和C的代码
// 只会使用到接口1,2,3三个方法
public class A {
public static void main(String[] args) {
B b = new B();
b.operation1();
b.operation2();
b.operation3();
}
}
// 只会使用到接口1,4,5三个方法
public class C {
public static void main(String[] args) {
D d = new D();
d.operation1();
d.operation4();
d.operation5();
}
}
从上面我们发现什么缺点:就是B类其实完全不需要实现4、5方法。D类完全不需要实现2、3方法。我们这样的代码没有遵循接口隔离,就会产生这样的缺点。
下面我们写一下遵循接口隔离的代码:
public interface Interface1 {
void operation1();
}
public interface Interface2 {
void operation2();
void operation3();
}
public interface Interface3 {
void operation4();
void operation5();
}
public class B implements Interface1, Interface2{
@Override
public void operation1() {
}
@Override
public void operation2() {
}
@Override
public void operation3() {
}
}
public class D implements Interface1, Interface3{
@Override
public void operation1() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
}
依赖倒转原则(Dependency Inversion Principle)
依赖倒转原则是指:抽象不应该依赖细节,细节应该依赖抽象。在Java中,抽象指的就是接口或抽象类,细节就是实现类。(面向接口编程)
依赖倒转原则是基于这样的设计理念:
相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。
使用接口或抽象类的目的是为了制定好规范,而不涉及任何具体操作,把展现细节的任务交给他们的实现类去完成。
应用实例
public class Demo {
public static void main(String[] args) {
Person person = new Person();
// 这里就只能接受邮件信息,非常不灵活
person.receive(new Email());
}
}
class Person {
public void receive(Email email ) {
System.out.println(email.getInfo());
}
}
class Email {
public String getInfo() {
return "电子邮件信息: hello,world";
}
}
遵循依赖倒转原则后:
public class Demo {
public static void main(String[] args) {
Person person = new Person();
//当为电子邮件时,传入邮件对象
person.receive(new Email());
//当为微信时,传入微信对象
person.receive(new WeiXin());
}
}
//定义接口
interface IReceiver {
public String getInfo();
}
//原电子邮件类,实现接口
class Email implements IReceiver {
public String getInfo() {
return "电子邮件信息: hello,world";
}
}
//增加微信
class WeiXin implements IReceiver {
public String getInfo() {
return "微信信息: hello,ok";
}
}
//方法中传入接口
class Person {
//这里我们是对接口的依赖
public void receive(IReceiver receiver ) {
System.out.println(receiver.getInfo());
}
}
注意事项和细节
1、依赖传递的方式
通过参数传递
通过构造方法传递
通过setter传递
2、底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
3、变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于 程序扩展和优化。
4、继承时遵循里氏替换原则
里氏替换原则
在父类中已经实现好的方法,在子类中一定不要重写,可以让子类换一个方法名称。如果非要用这个方法名称,那就再向上抽象一层,子类和父类继承来变为同一个级别。
我们想象一下,父类A中已经实现了一个方法test,逻辑是实现加法,在子类B中却重写了这个方法test,逻辑编程了减法。类C继承类A时不重写test方法,还是加法。那这样A类的子类对于test的方法就完全变了意思,用户在使用类B和类C是就容易产生问题。
面向对象编程中对继承的思考
1、继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
2、继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他类所继承,则当这个类要修改时,必须考虑到所有的子类,并且父类修改后,所有设计到子类的功能都有可能产生故障。
3、对于这些问题,在编程中就要遵循里氏替换原则。
开闭原则
对扩展开放(对提供方),对修改关闭(对使用方)。
在开发过程中,当需求变化时,尽量是通过扩展类的方式来实现,而不是通过修改已有的代码来实现。
应用实例
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
//**问题所在:此类属于使用方,但当我们需要扩展新的图形时,却要修改使用方,就不符合OCP原则
if (s.m_type == 1) {
drawRectangle(s);
}else if (s.m_type == 2) {
drawCircle(s);
}
}
//绘制矩形
public void drawRectangle(Shape r) {
System.out.println(" 绘制矩形 ");
}
//绘制圆形
public void drawCircle(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 GraphicEditor {
//接收Shape对象,调用draw方法
public void drawShape(Shape s) {
//直接调用公共方法即可,就算增加新的图形也无需修改此处,
//当多个地方调用时,更能体现OCP的重要性,这里只是简单举例
s.draw();
}
}
//Shape类,基类
abstract class Shape {
//抽象方法
public abstract void draw();
}
//[提供方]
class Rectangle extends Shape {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println(" 绘制矩形 ");
}
}
class Circle extends Shape {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println(" 绘制圆形 ");
}
}
迪米特法则
1、迪米特法则就是一个对象要对其他对象保持最少的了解。就是为了降低耦合度。
2、官方:迪米特法则,又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类,不管多么复杂,都尽量将逻辑封装在类的内部。对外除提供public方法,不对外泄露任何信息。
3、迪米特法则还有个更简单的定义:只与直接的朋友通信
4、直接朋友:对象和对象之间的耦合有很多种(依赖、关联、组合、聚合等),我们称出现在成员便利,方法参数,方法返回值中的类为直接朋友,而出现在局部变量中的类为陌生朋友。迪米特法则就是最好不要出现陌生朋友。
应用实例
public class Demo {
public static void main(String[] args) {
// print方法中存在陌生朋友,不遵循迪米特法则
print(new School());
}
static void print(School school) {
// 这里的Child就是陌生朋友,尽量少出现
List<Child> childLis = school.getChildLis();
for (Child child : childLis) {
System.out.println(child.name);
}
}
}
/**
* 学生
*/
class Child {
String name;
public Child(String name) {
this.name = name;
}
}
/**
* 学校
*/
class School {
/**
* 学校学生
*/
List<Child> childLis;
public List<Child> getChildLis() {
return childLis;
}
School() {
List<Child> list = new ArrayList<>();
list.add(new Child("张三"));
list.add(new Child("李四"));
childLis = list;
}
}
修改方法,遵循迪米特法则:
public class Demo {
public static void main(String[] args) {
// 在学校中增加打印学生名字的方法,这样代码就没有陌生朋友了
School school = new School();
school.print();
}
}
/**
* 学生
*/
class Child {
String name;
public Child(String name) {
this.name = name;
}
}
/**
* 学校
*/
class School {
/**
* 学校学生
*/
List<Child> childLis;
public List<Child> getChildLis() {
return childLis;
}
School() {
List<Child> list = new ArrayList<>();
list.add(new Child("张三"));
list.add(new Child("李四"));
childLis = list;
}
public void print() {
for (Child child : childLis) {
System.out.println(child.name);
}
}
}
注意事项和细节
1、迪米特法则的核心就是为了降低类之间的耦合。
2、迪米特法则只是为了降低不必要的依赖,不是要求完全没有依赖。
合成复用原则
在代码中尽量使用合成或聚合的方式,而不是使用继承的方式。
应用实例
尽量通过参数传递,通过成员变量传递,通过setter方法传递
设计原则核心思想
1、找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2、面向接口编程,而不是针对接口编程。
3、为了交互对象之间的松耦合设计而努力。
简单理解:
开闭原则是总纲,它指导我们要对扩展开放,对修改关闭;单一职责原则指导我们实现类的职责要单一;里氏替换原则指导我们不要破坏继承体系;依赖倒转指导我们要面向接口编程;接口隔离原则指导我们设计接口的时候要精简单一;迪米特法则指导我们要降低耦合。