- 设计模式
- 一、 概念
- 1.1 设计模式是什么
- 1.2 设计模式 的目的
- 二、 设计模式七大原则
- 2.1 单一职责原则
- 应用举例
- 注意事项
- 2.2 接口隔离原则
- 应用实例
- 2.3 依赖倒转原则(Dependence Inversion Principle)
- 应用举例
- 依赖关系传递的三种方式
- 小结
- 2.4 里氏替换原则
- IOO中的继承性思考和说明
- 里氏替换原则概述
- 应用举例
- 2.5 开闭原则ocp(Open Closed Principle)
- 概述
- 应用举例
- 2.6 迪米特法则
- 概述
- 应用举例
- 注意事项
- 2.7 合成复用原则
- 概述
- 3 设计原则核心思想
1.1 设计模式是什么
设计模式是对软件设计中普遍存在(反复出现)的各种问题所提出的解决方案。
1.2 设计模式 的目的设计模式是为了让程序(软件)拥有更好的
- 代码重用性(即:相同的功能代码,可以不用多次编写)
- 可读性(即:相当于代码的规范性,便于其他的程序员阅读和理解)
- 可扩展性(即:相当于代码的可维护性,当需要增加新的功能时,非常的方便)
- 可靠性(即:增加了新的功能代码,对原有的功能没有影响)
- 使程序呈现高内聚,低耦合的特性
设计模式原则就是程序员在编程时应当遵守的原则,也是各种设计模式的基础。(设计模式为什么这样设计的依据)
2.1 单一职责原则对于类来说,每一个类负责一项职责,如果一个类有两个或以上的职责,如果其中一个职责变更,可能会造成其他的职责发生错误,所以需要将该类的粒度进行分解。
应用举例public class SingleResponsibility1 {
public static void main(String[] args) {
vehicle vehicle = new vehicle();
vehicle.run("汽车");
vehicle.run("飞机");
vehicle.run("轮船");
}
}
class vehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路上运行");
}
}
运行结果:
汽车 在公路上运行 飞机 在公路上运行 轮船 在公路上运行
解决方案一:
public class SingleResponsibility2 {
public static void main(String[] args) {
vehicle vehicle = new vehicle();
AirVehicle airVehicle = new AirVehicle();
RoadVehicle roadVehicle = new RoadVehicle();
SeaVehicle seaVehicle = new SeaVehicle();
airVehicle.run("汽车,摩托");
roadVehicle.run("飞机,战斗机");
seaVehicle.run("舰艇,渔船");
}
}
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上运行。。");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在空中运行。。");
}
}
class SeaVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在海上运行。。");
}
}
解决方案二:
public class SingleResponsibility3 {
public static void main(String[] args) {
Vehicle1 vehicle1 = new Vehicle1();
vehicle1.roadRun("汽车");
vehicle1.airRun("飞机");
vehicle1.seaRun("轮船");
}
}
class Vehicle1 {
public void roadRun(String vehicle) {
System.out.println(vehicle + "在公路上运行。。");
}
public void airRun(String vehicle) {
System.out.println(vehicle + "在空中上运行。。");
}
public void seaRun(String vehicle) {
System.out.println(vehicle + "在海上运行。。");
}
}
注意事项
- 降低类的复杂度,一个类只负责单一的职责
- 提高类的可读性,可维护性。
- 降低变更带来的风险
- 通常情况,应当严格遵守单一职责原则,只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中的方法足够少,可以在方法级别上保证单一职责原则。
客户端不应该依赖它不也需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
应用实例- 接口Interface1中有5个方法。类B与类D实现该接口。
- 类A与类C分别依赖与类B与类D实现方法。
分析:
- 类A依赖与类B其中的1,2,3号方法,由于类B实现了接口Interface1中所有的方法,那么类B的其他4,5号方法没能被依赖实现。
- 类C依赖于类D中的1,4,5号方法,由于类D实现了接口Interface1中的所有方法,那么类D的其他2,3号方法没能被依赖实现。
- 这种接口的实现方法就没能实现接口隔离原则
代码实现:
public class InterfaceSegregation1 {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
D d = new D();
// 类A依赖类B实现B的方法
a.depend1(b);
a.depend2(b);
a.depend3(b);
// 类C依赖类D实现D的方法
c.depend1(d);
c.depend4(d);
c.depend5(d);
}
}
// 接口1
interface Interface1 {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1 {
@Override
public void operation1() {
System.out.println("B 实现了方法operation1");
}
@Override
public void operation2() {
System.out.println("B 实现了方法operation2");
}
@Override
public void operation3() {
System.out.println("B 实现了方法operation3");
}
@Override
public void operation4() {
System.out.println("B 实现了方法operation4");
}
@Override
public void operation5() {
System.out.println("B 实现了方法operation5");
}
}
class D implements Interface1 {
@Override
public void operation1() {
System.out.println("D 实现了方法operation1");
}
@Override
public void operation2() {
System.out.println("D 实现了方法operation2");
}
@Override
public void operation3() {
System.out.println("D 实现了方法operation3");
}
@Override
public void operation4() {
System.out.println("D 实现了方法operation4");
}
@Override
public void operation5() {
System.out.println("D 实现了方法operation5");
}
}
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface1 i) {
i.operation2();
}
public void depend3(Interface1 i) {
i.operation3();
}
}
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface1 i) {
i.operation4();
}
public void depend5(Interface1 i) {
i.operation5();
}
}
改进: 防止类A与类C实现不需要的方法
- 将原有的接口Interface1拆分成几个独立的接口,类A与类C分别于他们需要的接口简历依赖的关系,也就是采用了接口隔离原则。
- 这里将原来的Interface1拆分成三个接口。
代码实现:
public class InterfaceSegregation2 {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
D d = new D();
// 类A依赖类B实现B的方法
a.depend1(b);
a.depend2(b);
a.depend3(b);
// 类C依赖类D实现D的方法
c.depend1(d);
c.depend4(d);
c.depend5(d);
}
}
// 接口1
interface Interface1{
void operation1();
}
// 接口2
interface Interface2{
void operation2();
void operation3();
}
// 接口3
interface Interface3{
void operation4();
void operation5();
}
class B implements Interface1,Interface2{
@Override
public void operation1() {
System.out.println("B 实现了方法operation1");
}
@Override
public void operation2() {
System.out.println("B 实现了方法operation2");
}
@Override
public void operation3() {
System.out.println("B 实现了方法operation3");
}
}
class D implements Interface1,Interface3{
@Override
public void operation1() {
System.out.println("D 实现了方法operation1");
}
@Override
public void operation4() {
System.out.println("D 实现了方法operation4");
}
@Override
public void operation5() {
System.out.println("D 实现了方法operation5");
}
}
class A {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i) {
i.operation2();
}
public void depend3(Interface2 i) {
i.operation3();
}
}
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface3 i) {
i.operation4();
}
public void depend5(Interface3 i) {
i.operation5();
}
}
2.3 依赖倒转原则(Dependence Inversion Principle)
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象。
- 抽象不应该依赖细节,细节应该依赖抽象。
- 依赖倒转(倒置)的中心思想是面向接口编程。
- 依赖倒转原则是基于这样的设计理念,相对于细节的多变性,抽象的东西要稳定得多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
- 使用接口或抽象类的目的是指定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
这里以球员Kobe为例,被教练安排比赛位置。
public class DependenceInversion1 {
public static void main(String[] args) {
Kobe kobe = new Kobe();
kobe.commanded(new PG());
}
}
class Kobe {
void commanded(PG pg) {
System.out.println("被命令" + pg.play());
}
}
class PG {
String play() {
return "去打控球后卫!";
}
}
class SG{
String play(){
return "去打得分后卫!";
}
}
代码改进:
public class DependenceInversion2 {
public static void main(String[] args) {
Kobe kobe = new Kobe();
kobe.commanded(new PG());
kobe.commanded(new SG());
}
}
interface Position {
String play();
}
class PG implements Position {
@Override
public String play() {
return "去打组织后卫!";
}
}
class SG implements Position {
@Override
public String play() {
return "去打得分后卫!";
}
}
class Kobe {
void commanded(Position s) {
System.out.println("被命令" + s.play());
}
}
依赖关系传递的三种方式
- 接口传递:如上的改进方式使用的就是接口传递。
- 构造方法传递
- setter方式传递
代码实例:
public class DependencyPass {
public static void main(String[] args) {
// 接口传递
Kobe kobe = new Kobe();
kobe.commanded(new SG());
// 构造方法传递
// kobe kobe = new kobe(new SG());
// kobe.commanded();
//
// // setter方式传递
// Lin lin = new Lin();
// lin.setPosition(new PG());
// lin.commanded();
}
}
interface Position {
String play();
}
class PG implements Position {
@Override
public String play() {
return "去打组织后卫!";
}
}
class SG implements Position {
@Override
public String play() {
return "去打得分后卫!";
}
}
interface Player {
void commanded(Position pos);
}
class Kobe implements Player {
@Override
public void commanded(Position pos) {
System.out.println("被命令" + pos.play());
}
}
//class kobe {
//
// private Position pos;
//
// kobe(Position pos) {
// this.pos = pos;
// }
//
// void commanded() {
// System.out.println("被命令" + this.pos.play());
// }
//}
//interface Player {
//
// void commanded();
//
// void setPosition(Position pos);
//}
//
//class Lin implements Player {
//
// private Position pos;
//
// @Override
// public void commanded() {
// System.out.println("被命令" + this.pos.play());
// }
//
// @Override
// public void setPosition(Position pos) {
// this.pos = pos;
// }
//}
小结
- 底层模块尽量都要有抽象类或接口,或两者都有,程序的稳定性会更加好。
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间就存在一个缓冲层,利于程序扩展和优化。
- 继承时遵循里氏替换原则。
IOO中的继承性思考和说明
- 继承包含这样的一句定义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然他不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个集成体系造成破坏。
- 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当该类需要修改时,并且子类修改后,所有涉及到子类的功能都有可能产生故障。
- 所有引用基类的地方必须能透明地使用其子类的对象。(即尽可能地不要重写基类的方法,保持基类和子类中的方法统一—透明)。
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。
- 里氏替换原则说明:继承实际上让两个类耦合性增加,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
public class LiskovSubstitution1 {
public static void main(String[] args) {
A a = new A();
System.out.println("10-5=" + a.fun1(10, 5));
B b = new B();
System.out.println("10-5=" + b.fun1(10, 5));
System.out.println("10+5+9=" + b.fun2(10, 5));
}
}
class A {
Integer fun1(int a, int b) {
return a - b;
}
}
class B extends A {
@Override
Integer fun1(int a, int b) {
return a + b;
}
Integer fun2(int a, int b) {
return fun1(a, b) + 9;
}
}
改进
public class LiskovSubstitution2 {
public static void main(String[] args) {
A a = new A();
System.out.println("10-5=" + a.fun1(10, 5));
B b = new B();
System.out.println("10-5=" + b.fun2(10, 5));
System.out.println("10+5=" + b.fun1(10, 5));
}
}
// 创建一个更加基础的类作为基类
class base {
// 可以把更加基础的方法和成员写到此类中进行继承
}
class A extends base {
Integer fun1(int a, int b) {
return a - b;
}
}
class B extends base {
// 如果需要使用类A中的方法,这里使用组合的方式进行获取。
private A a = new A();
Integer fun1(int a, int b) {
return a + b;
}
// 使用类A中的减法
Integer fun2(int a, int b) {
return this.a.fun1(a, b);
}
}
2.5 开闭原则ocp(Open Closed Principle)
概述
- 开闭原则是编程中最基础、最重要的设计原则。
- 一个软件实体如类,模块和函数应该对扩展开放(提供方),对修改关闭(使用方),用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。
类图:
public class OpenClosed {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.draw(new Circle());
graphicEditor.draw(new Rectangle());
graphicEditor.draw(new Triangle());
}
}
// 图形编辑器类
class GraphicEditor {
public void draw(Shape s) {
if (s.type == 1) {
drawRectangle();
} else if (s.type == 2) {
drawCircle();
} else if (s.type == 3) {
drawTriangle();
}
}
public void drawRectangle() {
System.out.println(" 正在绘制矩形");
}
public void drawCircle() {
System.out.println(" 正在绘制圆形");
}
public void drawTriangle() {
System.out.println(" 正在绘制三角形");
}
}
// 图形类
class Shape {
int type;
}
// 矩形
class Rectangle extends Shape {
Rectangle() {
super.type = 1;
}
}
// 圆形
class Circle extends Shape {
Circle() {
super.type = 2;
}
}
class Triangle extends Shape {
Triangle() {
super.type = 3;
}
}
改进
public class OpenClosed2 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.draw(new Circle());
graphicEditor.draw(new Rectangle());
graphicEditor.draw(new Triangle());
graphicEditor.draw(new Other());
}
}
// 图形编辑器类
class GraphicEditor {
public void draw(Shape shape) {
shape.draw();
}
}
// 图形类
abstract class Shape {
int type;
abstract void draw();
}
// 矩形
class Rectangle extends Shape {
Rectangle() {
super.type = 1;
}
@Override
void draw() {
System.out.println(" 绘制矩形");
}
}
// 圆形
class Circle extends Shape {
Circle() {
super.type = 2;
}
@Override
void draw() {
System.out.println(" 绘制圆形");
}
}
// 三角形
class Triangle extends Shape {
Triangle() {
super.type = 3;
}
@Override
void draw() {
System.out.println(" 绘制三角形");
}
}
class Other extends Shape {
Other() {
super.type = 4;
}
@Override
void draw() {
System.out.println(" 绘制其他图形");
}
}
2.6 迪米特法则
概述
- 一个对象应该对其他对象保持最少的了解。
- 类与类关系越密切,耦合度越大。
- 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管怎么复杂,都尽量将逻辑封装在类的内部。
对外除了提供public方法,不对外泄露任何信息。 - 迪米特法则还有个更加简单的定义:只与直接的朋友通讯。
- 直接的朋友:每个对象都会与其他的对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象是朋友关系。
耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,
而出现局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的实行出现在类的内部。
public class Demeter {
public static void main(String[] args) {
// 公司管理类获取到打印所有人员的id
CompanyManager companyManager = new CompanyManager();
companyManager.printIdForAll(new TeamManager());
}
}
// 球员
class Player {
private int id;
int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
}
// 公司职员
class Employee {
private int id;
int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
}
// 球队管理类
class TeamManager {
// 获取球队所有球员
List getAllPlayer() {
List players = new ArrayList<>(11);
Player player;
// 增加11名球员到球队中
for (int i = 0; i < 11; i++) {
player = new Player();
player.setId(i);
players.add(player);
}
return players;
}
}
class CompanyManager {
// 获取公司所有职员
List getAllEmployee() {
List employees = new ArrayList<>(20);
Employee employee;
// 增加20名员工到公司中
for (int i = 0; i < 20; i++) {
employee = new Employee();
employee.setId(i);
employees.add(employee);
}
return employees;
}
void printIdForAll(TeamManager teamManager) {
System.out.println("打印球员id----------------------------------");
List allPlayer = teamManager.getAllPlayer();
for (Player player : allPlayer) {
System.out.println("球员ID==" + player.getId());
}
System.out.println("打印公司职员id-------------------------------");
List allEmployee = getAllEmployee();
for (Employee employee : allEmployee) {
System.out.println("公司职员ID==" + employee.getId());
}
}
}
改进
public class Demeter1 {
public static void main(String[] args) {
// 公司管理类获取到打印所有人员的id
CompanyManager companyManager = new CompanyManager();
companyManager.printIdForAll(new TeamManager());
}
}
// 球员
class Player {
private int id;
int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
}
// 公司职员
class Employee {
private int id;
int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
}
// 球队管理类
class TeamManager {
// 获取球队所有球员
List getAllPlayer() {
List players = new ArrayList<>(11);
Player player;
// 增加11名球员到球队中
for (int i = 0; i < 11; i++) {
player = new Player();
player.setId(i);
players.add(player);
}
return players;
}
// 打印球员id方法
void printPlayerId() {
System.out.println("打印球员id----------------------------------");
List allPlayer = getAllPlayer();
for (Player player : allPlayer) {
System.out.println("球员ID==" + player.getId());
}
}
}
class CompanyManager {
// 获取公司所有职员
List getAllEmployee() {
List employees = new ArrayList<>(20);
Employee employee;
// 增加20名员工到公司中
for (int i = 0; i < 20; i++) {
employee = new Employee();
employee.setId(i);
employees.add(employee);
}
return employees;
}
void printIdForAll(TeamManager teamManager) {
teamManager.printPlayerId();
System.out.println("打印公司职员id-------------------------------");
List allEmployee = getAllEmployee();
for (Employee employee : allEmployee) {
System.out.println("公司职员ID==" + employee.getId());
}
}
}
注意事项
- 迪米特法则的核心是降低类之间的耦合。
- 只是减少了每个类的不必要的依赖,因此迪米特法则只是要求降低类(对象)间的耦合关系,并不是要求他们之间完全没有依赖关系(这也是不可能的)。
概述
- 类与类之间尽量使用依赖,组合,聚合的方式,而不是使用继承的方式。
- 由于一个类A仅仅是需要调用类B中的方法,那么使用依赖,组合,聚合的方式比使用继承的方式更好,这样可以降低耦合。
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计而努力。



