《设计模式》设计模式的基本原则
《设计模式》单例模式
《设计模式》工厂模式
《设计模式》原型模式
《设计模式》建造者模式
《设计模式》适配器模式
《设计模式》桥接模式
《设计模式》装饰者模式
《设计模式》组合模式
《设计模式》外观模式
《设计模式》享元模式
《设计模式》代理模式
《设计模式》模板方法模式
《设计模式》命令模式
设计模式的基本原则就是设计模式设计的依据所在,是设计模式的基础。开发人员在编码时需要遵守这些基本原则,才能使得编写的代码可维护、可扩展、可重用、灵活性强,主要有六个基本原则:单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则和迪米特法则。
1. 单一职责原则
定义:一个类只负责一项职责,如类 A 负责两个不同职责:职责1 和职责 2. 当职责1 需求变更而改变类 A 时,可能造成职责2 执行错误,因此需要将类 A 的粒度分解为:A1, A2.
应用案例:
1、有一个交通工具类 Vehicle, 其包含实例方法 run()
,不同的交通工具(摩托车、汽车、飞机等)通过调用 run()
方法“启动”。
public class singleresponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.run("飞机");
vehicle.run("轮船");
}
}
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle+"在公路上运行...");
}
}
在案例1中的 run()
方法,类 Vehicle 负责了多项职责,即摩托车、汽车和飞机的运行,这就违反了单一职责原则。
2、为了遵守单一职责原则,应该让 Vehicle 类只负责一项职责,可以将 Vehicle 类分解为多个不同的类,根据不同的交通工具调用不同类的 run()
方法。
public class singleresponsibility {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("摩托车");
roadVehicle.run("汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("轮船");
}
}
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle+"公路运行");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle+"天空运行");
}
}
class WaterVehicle {
public void run(String vehicle) {
System.out.println(vehicle+"水中运行");
}
}
案例2中将类进行了分解,使得每个类只负责一项职责,遵守了单一职责原则。但是,可以发现将类进行分解之后,不仅需要增加新的类,而且还要改动客户端调用的代码。
3、为了减少代码的改动,并且还要遵守单一职责原则,可以选择在类中增加方法,达到这样的目的。
public class singleresponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.runAir("飞机");
vehicle.runWater("轮船");
}
}
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle+"在公路上运行...");
}
public void runAir(String vehicle) {
System.out.println(vehicle+"在天空上运行...");
}
public void runWater(String vehicle) {
System.out.println(vehicle+"在水中上运行...");
}
}
单一职责原则注意事项:
- 降低类的复杂度,一个类只负责一项职责。
- 提高类的可读性和可维护性。
- 降低变更引起的风险。
- 通常情况下,我们应当遵守单一职责原则,但是当逻辑非常简单时,可以在代码级别违反单一职责原则。只有类中方法数量足够少时,才可以在方法级别遵守单一职责原则。
2. 接口隔离原则
定义:一个类对另一个类的依赖应该建立在最小的接口,客户端不应该依赖它不需要的接口。
应用案例:
1、类 A 通过接口 Interface1 依赖类 B,但是只会使用接口的1,2,3方法,类 C 通过接口 Interface1 依赖类 D,但只会使用接口的1,4,5方法,其 UML 图和实现代码如下所示:
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 { //A类通过接口Interface1依赖(使用)B类,但是只会使用到operation1,2,3
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface1 i) {
i.operation2();
}
public void depend3(Interface1 i) {
i.operation3();
}
}
class C { //C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface1 i) {
i.operation4();
}
public void depend5(Interface1 i) {
i.operation5();
}
}
在案例1中,类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,而接口 Interface1 对于类 A 和类 C 来说不是最小接口,那么类 B 和类 D 必须实现它们不需要的方法,这就违反了接口隔离原则。
2、为了让类 B 和类 D 不用实现它们不需要的方法,可以将接口 Interface1 拆分为三个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系,其 UML 图和实现代码如下所示:
public class Segregation1 {
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());
}
}
//接口
interface Interface1 {
void operation1();
}
interface Interface2 {
void operation2();
void operation3();
}
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 { //A类通过接口Interface1依赖(使用)B类,但是只会使用到operation1,2,3
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i) {
i.operation2();
}
public void depend3(Interface2 i) {
i.operation3();
}
}
class C { //C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface3 i) {
i.operation4();
}
public void depend5(Interface3 i) {
i.operation5();
}
}
3. 依赖倒转原则
定义:
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象。
- 抽象不应该依赖细节,细节应该依赖抽象。
- 依赖倒转的核心思想是面向接口编程:相对于细节的多变性,抽象要稳定的多,以抽象为基础搭建的架构比以细节为基础搭建的架构稳定的多。在面向对象编程语言中,这里的抽象就是指接口或者抽象类,细节就是指具体的实现类。
- 使用接口或抽象类是制定好规范,不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
应用案例:
1、有一个 Person 类,通过调用它的实例方法 receive()
来接收信息。
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";
}
}
class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
在案例1中,如果获取的对象是微信,短信等,则新增类 Wechat 同时 Person 也要增加相应的接收方法,这就使得高层模块依赖了底层模块,而不是依赖其抽象。
2、引入一个抽象的接口 IReceiver
表示接收者,Eemail
和 Wechat
各自实现接口 IReceiver
,之后 Person
类与接口 IReceiver
发生依赖,符合依赖倒转原则。
public class DependencyInversion {
public static void main(String[] args) {
//客户端无需改变
Person person = new Person();
person.receive(new Email());
person.receive(new Wechat());
}
}
interface IReceiver {
String getInfo();
}
class Email implements IReceiver{
@Override
public String getInfo() {
return "电子邮件信息:hello,world";
}
}
class Wechat implements IReceiver {
@Override
public String getInfo() {
return "微信消息:hello,world";
}
}
class Person {
public void receive(IReceiver receiver) {
System.out.println(receiver.getInfo());
}
}
依赖关系传递的三种方式:
1、接口传递
public class DependencyPass {
public static void main(String[] args) {
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(new HUAWEI());
}
}
interface IOpenAndClose {
public void open(ITV tv);
}
interface ITV {
public void play();
}
class HUAWEI implements ITV {
@Override
public void play() {
System.out.println("打开华为电视机");
}
}
class OpenAndClose implements IOpenAndClose {
@Override
public void open(ITV tv) {
tv.play();
}
}
2、构造器传递
public class DependencyPass {
public static void main(String[] args) {
OpenAndClose openAndClose = new OpenAndClose(new HUAWEI());
openAndClose.open();
}
}
interface IOpenAndClose {
void open();
}
interface ITV {
void play();
}
class HUAWEI implements ITV {
@Override
public void play() {
System.out.println("打开华为电视机");
}
}
class OpenAndClose implements IOpenAndClose {
public ITV tv;
public OpenAndClose(ITV tv) {
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
3、通过 setter 方式传递
public class DependencyPass {
public static void main(String[] args) {
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.setTv(new HUAWEI());
openAndClose.open();
}
}
interface IOpenAndClose {
void open();
void setTv(ITV tv);
}
interface ITV {
void play();
}
class HUAWEI implements ITV {
@Override
public void play() {
System.out.println("打开华为电视机");
}
}
class OpenAndClose implements IOpenAndClose {
private ITV tv;
@Override
public void open() {
tv.play();
}
@Override
public void setTv(ITV tv) {
this.tv = tv;
}
}
依赖倒转原则的注意事项:
- 低层模块尽量都要有抽象类或接口,或两者都有,这样程序的稳定性才更好。
- 变量的声明类型尽量是抽象类或接口,这样变量的引用和实际对象间存在一个缓冲,有利于程序扩展和优化。
4. 里氏替换原则
定义:如果对每个类型为 T1 的对象 o1, 都有类型为 T2 的对象 o2, 使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换而言之,所有引用基类的地方必须能够透明地使用其子类的对象。
应用案例:
1、类 A 作为基类,类 B 继承类 A 并重写了类 A 中的 func()
方法。
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3="+a.func(11, 3));
System.out.println("--------------------");
B b = new B();
System.out.println("11-3="+b.func(11, 3));//本意是求出11-3,却变成11+3
}
}
//A类
class A {
//增加新的功能,这里重写了A类的方法,可能是无意识的
public int func(int num1, int num2) {
return num1 - num2;
}
}
class B extends A {
//增加新的功能,无意识地重写了A类的方法
public int func(int a, int b) {
return a + b;
}
}
- 在案例1中,类 B 重写了基类 A 的
func1()
方法,即使可能是无意识的重写,原本行为是实现两数相加却变成了两数相减,改变了程序的行为这就违背了里氏替换原则。 - 在实际场景中,通过重写父类的方法完成新的功能这种方式实现起来确实简单,但是将会导致整个继承体系的复用性会比较差,特别是在多态使用频繁的时候。
2、为了遵守里氏替换原则,可以将原有的继承关系去掉,让原来的父子类都继承一个基类,采用依赖、聚合和组合关系替代继承关系,其 UML 图和实现代码如下所示:
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("--------------------");
B b = new B();
System.out.println("11+3="+b.func1(11, 3));//这里本意是求出11+3
System.out.println("11-3="+b.func2(11, 3)); //11-3
}
}
//创建一个更加基础的基类
class Base {
//把更加基础的方法和成员写到Base类
}
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();
public int func1(int a, int b) {
return a + b;
}
//仍然想使用A的方法
public int func2(int a, int b) {
return a.func1(a,b);
}
}
里氏替换原则的注意事项:
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。
- 继承让两个类的耦合性变得更强,为了降低类间的耦合性,适当情况下可以使用聚合、组合和依赖来解决问题。
5. 开闭原则
定义:
- 一个软件实体类,模块和函数应该对扩展对扩展开放(对服务提供方开放),对修改关闭(对服务使用方),用抽象构建框架用实体扩展细节。
- 当软件需求发生变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码实现变化。
应用案例:
1、有一个画图类 Graphical
, 通过调用方法 drawShape(Shape s)
进行画图,根据传入的 s.shape
可以画出不同的图形,其 UML 图和实现代码如下所示:
public class OCP {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
class GraphicEditor {
public void drawShape(Shape s) {
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("绘制圆形");
}
}
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
在案例1中,如果需要新增一个可以画三角形的功能,不仅需要新增类 Triangle
继承 Shape
,与此同时还要修改类 GraphicEditor
,在 drawShape(Shape s)
新加一个分支,并且还要增加一个 drawTriangle(Shape s)
方法。这一系列行为都是对使用方的修改,违背了开闭原则。
2、为了遵守开闭原则,可以考虑将 Shape
作为抽象类并提供抽象方法 draw()
,让子类实现抽象类 Shape
,每当有新的图形种类增加时,可以直接继承 Shape
并实现 draw()
方法,无须对使用方进行修改。
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 {
public void drawShape(Shape s) {
s.draw();
}
}
abstract class Shape {
public abstract void draw();
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
class OtherGraphic extends Shape {
@Override
public void draw() {
System.out.println("绘制其他图形");
}
}
6. 迪米特法则
定义:
- 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。不管被依赖的类多么复杂,都尽量将逻辑封装在类的内部,对外除了
public
方法不泄露任何信息。 - 迪米特法则还有个更简单的定义:只与直接的朋友通信。
- 每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式有很多:依赖、关联、组合和聚合等。
- 其中,出现在成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类不是朋友关系,陌生的类最好不要以局部变量的形式出现在类的内部。
应用案例:
1、有一个学校,下面有总部和各个学院,要求打印出学校总部员工和各个学院员工的 ID.
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 void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
class CollegeManager {
//返回学院所有员工
public List<CollegeEmployee> getAllEmployee() {
ArrayList<CollegeEmployee> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id="+i);
list.add(emp);
}
return list;
}
}
class SchoolManager {
//返回学校总部所有员工
public List<Employee> getAllEmployee() {
ArrayList<Employee> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学校总部员工id="+i);
list.add(emp);
}
return list;
}
//输出学校总部和学院员工信息
public void printAllEmployee(CollegeManager sub) { //CollegeManager作为printAllEmployee局部变量
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());
}
}
}
在案例1中,CollegeEmployee
以局部变量的形式出现在 SchoolManager
类中,作为 SchoolManager
的陌生类,并不是它的直接朋友,这就违背了迪米特法则。
2、为了遵守迪米特法则,在类中应该避免这种非直接朋友关系的耦合,将 printEmployee()
方法封装到类 CollegeManager
中,而将类 CollegeManager
作为类 SchoolManager
的直接朋友。
public class Demeter1 {
public static void main(String[] args) {
System.out.println("使用迪米特法则的改进");
//创建了一个SchoolManager对象
SchoolManager schoolManager = new SchoolManager();
//输出学院员工id 和 学校总部员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
class CollegeManager {
//返回学院所有员工
public List<CollegeEmployee> getAllEmployee() {
ArrayList<CollegeEmployee> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id="+i);
list.add(emp);
}
return list;
}
public void printEmployee() {
List<CollegeEmployee> list1 = this.getAllEmployee();
System.out.println("--------------分公司员工-------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
class SchoolManager {
//返回学校总部所有员工
public List<Employee> getAllEmployee() {
ArrayList<Employee> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学校总部员工id="+i);
list.add(emp);
}
return list;
}
//输出学校总部和学院员工信息
public void printAllEmployee(CollegeManager sub) {
//将获取学院员工的方法封装到CollegeManager类里面
sub.printEmployee();
List<Employee> list2 = this.getAllEmployee();
System.out.println("-------------学校总部员工-------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
迪米特法则的注意事项:
- 核心思想是降低类之间的耦合。
- 对每个类减少不必要的依赖,降低类之间的耦合关系,并不是严格要求完全没有依赖关系。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/157011.html