设计模式-原则
七大设计原则
所有原则都为了降低类之间的耦合。
一、单一职责
- 降低类复杂度,一个类负责一项职责。
- 提高类可读性,可维护性。
- 降低变更引起的风险。
- 通常情况下,应遵循。只有逻辑够简单,才可在代码级别(通过if...else...)违反;只有类方法数量足够少,才可在方法级别(定义不同方法实现不同操作)保持单一职责
二、接口隔离
客户端不应依赖不需要的接口,即对一个类的依赖,应该建立在最小接口上。
如:接口Interface有1、2、3等方法,B类实现了Interface接口。A类通过接口依赖B类,但A只需要用到接口中1、2两个方法。
按接口隔离原则应把接口分离:
1、Interface分成两个接口:Interface1有方法1、2;Interface2有方法3;
2、B类实现接口Interface1即可;
3、如果后续某个类需要用到Interface中1、2、3方法,再让B实现Interface2。
三、依赖倒转
- 底层模块尽量使用抽象类或接口,或两者都有,程序稳定性更好
- 变量声明类型尽量用抽象类或接口。这样变量引用和实际对象存在一个缓冲层(即接口),利用程序扩展和优化。
- 继承时遵循里氏替换原则
//客户使用端
public class Client {
public static void main(String[] args){
OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
openAndCloseTv.open(new Changhong());
openAndCloseTv.open(new Kangjia());
}
}
//开关接口
interface IOpenAndCloseTv{
public void open(ITV tv);
}
//实现
class OpenAndCloseTv implements IOpenAndCloseTv{
@Override
public void open(ITV tv) {
tv.play();
}
}
//tv类型接口
interface ITV{
public void play();
}
//定义不同tv类型
class Changhong implements ITV{
@Override
public void play() {
System.out.println("长虹电视打开。。。");
}
}
class Kangjia implements ITV{
@Override
public void play() {
System.out.println("康佳电视打开了。。。");
}
}
结果:
长虹电视打开。。。
康佳电视打开了。。。
依赖关系传递三种方式:
- 接口传递
//开关接口
interface IOpenAndCloseTv{
public void open(ITV tv);
}
//实现
class OpenAndCloseTv implements IOpenAndCloseTv{
@Override
public void open(ITV tv) {
tv.play();
}
}
---------------------使用
public static void main(String[] args){
OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
openAndCloseTv.open(new Changhong());
openAndCloseTv.open(new Kangjia());
}
- 构造方法传递
//开关接口
interface IOpenAndCloseTv{
public void open();
}
//实现
class OpenAndCloseTv implements IOpenAndCloseTv{
private ITV tv; // 成员变量
public OpenAndCloseTv(ITV tv){
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
---------------------使用
public static void main(String[] args){
OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv(new Changhong());
openAndCloseTv.open();
OpenAndCloseTv openAndCloseTv2 = new OpenAndCloseTv(new Kangjia());
openAndCloseTv2.open();
}
- setter方法传递
//开关接口
interface IOpenAndCloseTv{
public void open();
public void setTv(ITV tv)
}
//实现
class OpenAndCloseTv implements IOpenAndCloseTv{
private ITV tv; // 成员变量
@Override
public void setTv(ITV tv){
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
---------------------使用
public static void main(String[] args){
OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
openAndCloseTv.setTv(new Changhong();
openAndCloseTv.open();
}
四、里氏替换
一个类被其他类所继承,该类修改时,必须考虑所有子类。父类修改后,所有子类有可能产生故障。
如何正确使用继承?--》里氏替换原则
- 使用基类的地方必须能透明使用子类对象。
- 使用继承时,在子类中尽量不要重写父类方法。
- 继承实际让两个类耦合性增强,在适当情况下可通过聚合,组合,依赖解决问题,不要通过继承。
如某类子类需要改写父类方法,应把子类提升和父类一样的档次,把共同方法抽取,让子类和父类继承同一个更基层的基类。
若要改变父,就不要做父子关系了,做兄弟关系吧。
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();
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));
}
}
class A {
public int func1(int num1,int num2){
return num1 - num2;
}
}
class B extends A{
// 重新了A 类方法,可能是无意识
@Override
public int func1(int a,int b){
return a + b;
}
public int func2(int a,int b){
return func1(a,b)+9;
}
}
通过里氏替换原则:
public class Liskov2 {
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();
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));
System.out.println("11-3="+ b.func3(11,3)); //实际调用的是A类的方法。
}
}
class base{
//共有的更基础的方法、成员变量定义到该类
//如:
public int funcMult(int num1,int num2){
return num1*num2;
}
}
class A extends base{
public int func1(int num1,int num2){
return num1 - num2;
}
}
class B extends base{
private A aobj = new A(); //通过组合方式解决继承问题-step1
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 aobj.func1(a,b); //通过组合方式解决继承问题-step2
}
}
----------结果
11-3=8
1-8=-7
----------------------------
11+3=14
1+8=9
11+3+9=23
11-3=8
五、开闭原则
- 最基础、最重要的设计原则
- 一个实体如类、模块、函数应【扩展开发(对提供方),修改关闭(对使用方)】。抽象构建框架,实现扩展细节。
- 软件需变化,尽量通过扩展代码而不是修改已有代码。
- 编程中遵循其他原则,及设计模式目的就是遵循开闭原则(核心)。
不遵循OCP原则:
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对象,调用draw方法
public void drawShape(Shape s) {
if (s.m_type == 1){
drawRectangle();
}else if (s.m_type == 2){
drawCircle();
}else if (s.m_type == 3){ //新增图形,使用方需要进行修改
drawTriangle();
}
}
public void drawRectangle(){ System.out.println(" 绘制矩形 "); }
public void drawCircle() { System.out.println(" 绘制圆形 "); }
//新增图形,使用方需要进行修改
public void drawTriangle() { System.out.println(" 绘制三角形 "); }
}
//定义各种图形
//Shape类,基类,
abstract 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原则:
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 {
//接收Shape对象,调用draw方法
public void drawShape(Shape s) { s.draw(); //只需要新增图形就可以,不需要修改代码。 }
}
//Shape类,遵守ocp原则通过创建一个基类,
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(" 绘制其它图形 "); }
}
----------结果
绘制矩形
绘制圆形
绘制三角形
绘制其它图形
六、迪米特法则
核心是为了降低类之间的耦合。
- 一个对象对其他对象保持最少了解。
- 也叫【最少知道原则】,即一个被依赖的类不管多复杂,尽量将逻辑封装类内部,对外只提供public方法,不对外泄露任何信息。
- 更简单定义:只与【直接朋友(两对象耦合即朋友,出现在成员变量,方法参数,方法返回值中的类为直接的朋友,出现在局部变量中的类不是直接的朋友,称陌生类)】通信。
- 耦合方式:依赖、关联、组合、聚合等。
- 陌生类最好不要以局部变量出现在类内部。
非迪米特法则:
//客户端
public class Demeter1 {
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() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id= " + i);
list.add(emp);
}
return list;
}
}
//学校管理类
//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
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());
}
}
}
-----------结果
------------学院员工------------
学院员工id= 0
学院员工id= 1
学院员工id= 2
学院员工id= 3
学院员工id= 4
学院员工id= 5
学院员工id= 6
学院员工id= 7
学院员工id= 8
学院员工id= 9
------------学校总部员工------------
学校总部员工id= 0
学校总部员工id= 1
学校总部员工id= 2
学校总部员工id= 3
学校总部员工id= 4
遵循迪米特法则:
//客户端
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() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id= " + i);
list.add(emp);
}
return list;
}
//输出学院员工的信息
public void printEmployee() {
//获取到学院员工
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("------------学院员工------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//学校管理类
//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
Employee emp = new Employee();
emp.setId("学校总部员工id= " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
//分析问题
//1. 将输出学院的员工方法,封装到CollegeManager
sub.printEmployee();
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------学校总部员工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
----------结果
~~~使用迪米特法则的改进~~~
------------学院员工------------
学院员工id= 0
学院员工id= 1
学院员工id= 2
学院员工id= 3
学院员工id= 4
学院员工id= 5
学院员工id= 6
学院员工id= 7
学院员工id= 8
学院员工id= 9
------------学校总部员工------------
学校总部员工id= 0
学校总部员工id= 1
学校总部员工id= 2
学校总部员工id= 3
学校总部员工id= 4
七、合成复用原则
- 尽量使用组合/聚合的方式,不是使用继承。
类之间关系
所有都可以归为依赖。
- 依赖:
- 通过方法参数传递依赖进来。
- 类中用到了对方。
- 类成员属性
- 方法返回类型
- 方法接收参数
- 方法中使用
- 泛化(继承):依赖关系的特例
- 泛化关系就是继承关系。
- 实现:依赖关系的特例
- 类实现了某一接口。
- 关联:依赖关系的特例。类与类之间的联系。
- 具有导航性(单向或双向),多重性(一对一、一对多等)
- 如人和身份证类
- 聚合:关联关系的特例。
- 添加类属性,通过方法设置进来。
- 整体与部分的关系,整体与部分可以分开。即可有可无。
- 导航性(a聚合b还是b聚合类),多重性(单聚合-聚合一个还是多聚合-聚合多个)
- 如:人和身份证、台式主机和显示器。
- 组合:关联关系的特例。
- 构建属性直接new一个出来。
- 整体与部分的关系,整体与部分不可分开。
- 随着当前类创建而创建,销毁而销毁。
- 如:人和自己的头
- 级联删除即组合关系。
设计原则核心思想
- 找出可能需变化之处独立出来,不要和不需变化的代码混写。
- 针对接口编程,不是具体实现类编程
- 为交互对象之间低耦合设计努力