一. 前言
1.1 设计模式的重要性
-
软件工程中,设计模式(design pattern) 是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,这个术语由 Erich Gamma 在1990年代从 建筑学 领域引入到 计算机中的
-
摩天大厦 vs 简易房
-
实际工作经历来说:当一个项目开发完成后,客户提出新增需求,要有一条设计模式 提供扩展性
-
人员离职,项目维护性
-
design patten 在软件中的哪里?
面向对象(OO) ----> 功能模块[设计模式+算法(数据结构)] ----> 框架[使用多种设计模式] ---> 架构[集群]
1.2 设计模式的目的
随着系统功能的逐步增加,各种功能相互依赖
再加上团队合作开发,怎么能确保不出现问题?
耦合性 内聚性 可维护性 可扩展性 重用性 灵活性
- 重用性 :相同的代码只写一遍 ,其他地方引用这里 , 以后修改方便
- 可读性 :编程规范 团队
- 可扩展 :增加新功能
- 可靠性 :新的功能增加,对之前的功能不能造成破坏
- 高内聚 低耦合
“懂了设计模式,就懂了面向对象分析和设计的精要”
1.3 浅谈七大原则
design pattern 原则,其实就是 各种设计模式的基础
也是程序设计时应当遵守的规则
二.七原则
2.1 单一职责原则
一个类只应该负责一项职责
多个职责的话,修改了其中一个 可能 导致另一个崩溃
package com.ifeng.Princleple.aSingleResponsibility;
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("Airplane");
vehicle.run("Car");
}
}
/*
run() 违反了单一职责原则
可以根据工具的不同 分解成不同的类
*/
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle + "在公路上运行");
}
}
改进1
根据交通工具的不同,分解成不同的类即可
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("car");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("Airplane");
}
}
class RoadVehicle{
public void run(String vehicle){
System.out.println(vehicle + "fly in the sky");
}
}
class AirVehicle{
public void run(String vehicle){
System.out.println(vehicle + "fly in the road");
}
}
改进2
方法级别上 遵循 单一职责
改动小
class Vehicle2{
/*
没有太多的改动,只是增加了方法
虽然这样在类级别上没有遵守singleResponsibility原则,但是在方法上遵守了
*/
public void run(String vehicle){
System.out.println(vehicle + "run in the road");
}
public void runAir(String vehicle){
System.out.println(vehicle + "fly in the Air");
}
public void runWater(String vehicle){
System.out.println(vehicle + "swim on the water");
}
}
-
- 降低类的复杂度,一个类一个职责
-
- 提高可读性 可维护性
-
- 降低变更引起的风险
-
- 只有逻辑拆分的足够简单,
2.2 接口隔离原则
C端不应该 依赖它不需要的接口
一个类对另一个类的依赖应该建立在最小的接口上
package com.ifeng.Princleple.bInterfaceSegregation;
/**
* 一个类对另一个类的依赖建立在最小接口上
* 类A通过interface1依赖classB,classC通过 interface1 依赖 classD,如果 interface1 对于类A 和 类C不是最小接口,那么B D需要实现他们不需要的方法
* 按照接口隔离原则处理:将interface1拆分成几个接口,Class A & Class C 分别与他们需要的建立关系,也就是接口隔离原则
*
*/
public class Segregation1 {
}
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1{
@Override
public void operation1() {
System.out.println("B implements func() operation1");
}
@Override
public void operation2() {
System.out.println("B implements func() operation2");
}
@Override
public void operation3() {
System.out.println("B implements func() operation3");
}
@Override
public void operation4() {
System.out.println("B implements func() operation4");
}
@Override
public void operation5() {
System.out.println("B implements func() operation5");
}
}
class D implements Interface1{
@Override
public void operation1() {
System.out.println("D implements func() operation1");
}
@Override
public void operation2() {
System.out.println("D implements func() operation2");
}
@Override
public void operation3() {
System.out.println("D implements func() operation3");
}
@Override
public void operation4() {
System.out.println("D implements func() operation4");
}
@Override
public void operation5() {
System.out.println("D implements func() operation5");
}
}
// class A 通过interface 依赖使用B,但是只会用到123 方法
class A{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface1 i){
i.operation2();
}
public void depend3(Interface1 i){
i.operation3();
}
}
// class A 通过interface 依赖使用B,但是只会用到145 方法
class C{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface1 i){
i.operation4();
}
public void depend3(Interface1 i){
i.operation5();
}
}
//完成后, B D 实现了多余的方法
//解决方式:将interface拆分成多个子接口
改进
package com.ifeng.Princleple.bInterfaceSegregation;
/**
* 1 interface对于 class A & class C不是最小接口
* 2. 将interface 根据实际情况 拆分成最小的接口
*/
public class Segregation2 {
}
//根据实际情况 拆分成了三个接口
interface Interface2{
void operation1();
}
interface Interface3{
void operation2();
void operation3();
}
interface Interface4{
void operation4();
void operation5();
}
2.3 依赖倒转原则
1.高层模块不应该依赖低层模块,两者都应该依赖其抽象
2.抽象不应该依赖袭击,细节应该依赖抽象
3.依赖倒转(倒置)的中心思想是买男香接口编程
4。依赖倒置的设计理念:相对于细节的多变性,抽象的东西相对稳定。以抽象为基础搭建的架构比以细节为基础的架构要稳定多。
在java中,抽象:接口或抽象类,细节就是具体的实现类
5.使用接口 的 优点:制定好规范,而不需要涉及任何具体的操作。把展现细节的任务交给他们的实现类去完成
public class DependecyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public String getInfo(){
return "电子邮件信息:hello email";
}
}
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
}
方式1 分析
- 1 . 简单
- 2 . 如果获取的对象是 短信 微信 等,需要新增class 同时Person也要增加相应的接收方法
- 3 . 引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖
- 因为Email WeChat 等属于接收的范围,他们各自实现IReceiver接口就ok , 这就是依赖倒转原则
interface IReceiver{
public String getInfo();
}
class Email2 implements IReceiver{
public String getInfo(){
return "Email";
}
}
class WeChat implements IReceiver{
public String getInfo(){
return "WeChat";
}
}
2.3.2 依赖传递的是三种方式
2.3.2.1 接口传递
// TODO 1 通过接口传递实现依赖
interface IOpenAndClose{
public void open(ITV tv);//抽象方法,接收接口
}
interface ITV{//ITV接口
public void play();
}
class ChangHong implements ITV{
@Override
public void play() {
System.out.println("ChangHong play");
}
}
//实现接口
class OpenAndClose implements IOpenAndClose{
@Override
public void open(ITV tv) {
tv.play();
}
}
2.3.2.2 构造方法传递
interface IOpenAndClose2{
public void open();
}
interface ITV2{
public void play();
}
class OpenAndClose2 implements IOpenAndClose2{
public ITV2 itv2;
// TODO 2 通过构造方法传递依赖
public OpenAndClose2(ITV2 itv2){
this.itv2 = itv2;
}
@Override
public void open() {
this.itv2.play();
}
}
2.3.2.3 setter 方法传递
interface IOpenAndClose3{
public void open();
public void setTv(ITV3 itv3);
}
interface ITV3{
public void play();
}
class OpenAndClose3 implements IOpenAndClose3{
private ITV3 itv3;
@Override
public void open() {
this.itv3.play();
}
@Override
public void setTv(ITV3 itv3) {
this.itv3 = itv3;
}
}
-
- 低层模块尽量用 抽象类 接口,更稳定
-
- 变量的声明类型尽量是抽象类或接口,变量引用 —缓冲层—实际对象 , 有利于扩展 优化
-
- 继承时 遵循 里氏替换 原则
2.4 里氏替换原则
-
- 继承包含这样一层关系:父类中凡事已经实现好的方法,实际上是在设定规范 和 契约,虽然它不强制要求所有子类遵守
但是子类任意修改,就会造成系统破坏
- 继承包含这样一层关系:父类中凡事已经实现好的方法,实际上是在设定规范 和 契约,虽然它不强制要求所有子类遵守
-
- 继承 有便利 也有 弊端 。带来入侵性,可移植性降低,增加对象间的耦合
如果 一个类 被 其他的类所继承,则这个类需要修改的时,必须考虑所有子类,父类修改 可能影响所有子类
- 继承 有便利 也有 弊端 。带来入侵性,可移植性降低,增加对象间的耦合
-
- 如何使用 继承 ? ====> 里氏替换原则
class A{
public int func1(int num1,int num2){
return num1 - num2;
}
}
class B extends A{
//无意识重写了func1
@Override
public int func1(int num1,int num2){
return num1 + num2;
}
/*
这里重写了A.func1 ,实际上A 就无效了,整体继承性 复用性 就变差
解决:
1) A B 共同抽象出一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等方式
2)
*/
public int func2(int num1,int num2){
return func1(num1,num2) + 9;
}
}
class Base{
//更基础的方法写到Base类中
}
class A2 extends Base{
public int func1(int num1,int num2){
return num1 - num2;
}
}
class B2 extends Base{
public int func1(int num1,int num2){
return num1 + num2;
}
/*
如果需要B用到A类的方法,使用组合关系
*/
private A2 a2 = new A2();
public int fun3(int a,int b){
return this.a2.func1(a,b);
}
public int func2(int num1,int num2){
return func1(num1,num2) + 9;
}
}
2.5 开闭原则
-
- 开闭原则 , 编程中最基本的,最重要的设计原则
-
- 模块 函数 英爱对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现构建细节
-
- 当软件需要变化时,尽量扩展实体行为, 而不是通过修改已有的代码来实现
-
- 其他原则设计的目的就是为了遵循开闭原则
//这是一个用于绘图的类【使用方】
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("drawRectangle");
}
public void drawCircle(Shape r){
System.out.println("drawCircle");
}
public void drawTriangle(Shape r){
System.out.println("drawTriangle");
}
}
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 = 3;
}
}
- 优点:好理解,容易操作
- 缺点:违反来设计模式的ocp原则,比如 现在要新增一个 Triangle , GraphicEditor 修改的地方过多
改进
//这是一个用于绘图的类【使用方】
class GraphicEditor2{
//接收Shape对象,然后根据type 来绘制不同的图形
public void drawShape(Shape2 s){
s.draw();
}
}
abstract class Shape2{
int m_type;
public abstract void draw();
}
class Rectangle2 extends Shape2{
Rectangle2(){
super.m_type = 1;
}
@Override
public void draw() {
System.out.println("Rectangle2");
}
}
class Circle2 extends Shape2{
Circle2(){
super.m_type = 2;
}
@Override
public void draw() {
System.out.println("Circle2");
}
}
class Triangle2 extends Shape2{
Triangle2(){
super.m_type = 3;
}
@Override
public void draw() {
System.out.println("Triangle2");
}
}
2.6 迪米特原则
-
- 一个对象应该对应其他读喜庆保持最少的了解
-
- class & class 关系越紧密,耦合度越高
-
- demeter 又叫 最少知道原则 ,只与直接朋友通信,一个类对自己依赖的类知道的越少越好
尽量将逻辑疯长在类内部,对除了提供的public方法,不对外泄漏其他信息
- demeter 又叫 最少知道原则 ,只与直接朋友通信,一个类对自己依赖的类知道的越少越好
-
- 直接朋友:每个对象都会 与 其他对象 存在耦合关系,存在耦合关系 就是 直接朋友
耦合的方式有很多:依赖 关联 组合 聚合
我们称出现 成员变量 方法参数 方法返回值 重的类为直接的朋友,
而出现在局部变量中的类不是直接朋友
也就是说:陌生的类最好不要以局部变量的形式出现在类的内部
- 直接朋友:每个对象都会 与 其他对象 存在耦合关系,存在耦合关系 就是 直接朋友
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(){
List<CollegeEmployee> list = new ArrayList<>();
for(int i = 0;i < 10; i++){
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工 id = " + i);
list.add(emp);
}
return list;
}
//TODO 迪米特法则的改进
public void printEmployee(){
//获得到学院员工
List<CollegeEmployee> allEmployee = getAllEmployee();
System.out.println("------------学院员工-------------");
allEmployee.forEach(System.out::println);
}
}
//SchoolManager类的直接朋友类:Employee CollegeManager
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;
}
// TODO collegeEmployee 不是直接的朋友 而是一个陌生类
// 陌生类 以 局部变量 的方式出现
void printAllEmployee(CollegeManager sub){
//获得到学院员工
// List<CollegeEmployee> allEmployee = sub.getAllEmployee();
//TODO 直接把输出学院员工的方法封装到 CollegeManger 中
sub.printEmployee();
}
2.7 合成服用原则
- 找出应用中可能需要变化之处,把他们独立出来,和 不需要变化的code 区分
- 针对接口编程,而不是针对实现编程
- 为交互对象之间的松耦合而设计