设计模式的七大原则
设计模式之七大编程原则
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 迪米特法则
- 合成复用原则
笔记总结来源于B站:B站尚硅谷——Java设计模式
一、单一职责原则
简而言之,在设计程序的过程中确保一个类或者方法只负责一个职责,或者说只执行一个功能的设计原则被称为单一职责原则
看如下代码:
public class Demo01{
public static void main(String[] args){
Animal animal = new Animal();
animal.eat("小猫");
animal.eat("老鼠");
animal.eat("鱼"); //我吃我自己!!!?
}
}
class Animal{
void eat(String name){
System.out.println(name+"在吃鱼!");
}
}
小猫在吃鱼!
老鼠在吃鱼!
鱼在吃鱼!
根据以上代码,虽然写的很蠢但能反应出一个问题,Animal的家住海边,管的比较宽,导致了小猫、老鼠、鱼都是吃鱼。
这样的代码非常非常非常的不合理,鱼怎么能吃自己呢?
1.2 修改思路
Animal实在是太笼统了,因此他的职位也特别的多,我们需要细分下来到小猫、老鼠、鱼的类,继承或者去实现Animal里面的吃方法
这样子小猫的类只管小猫、老鼠的类只管老鼠、鱼的类只管鱼,遵守单一职责原则
public class Demo01{
public static void main(String[] args){
Animal animal = new Animal();
animal.CatEat("小猫");
animal.MouseEat("老鼠");
animal.FishEat("鱼");
}
}
class Animal{
void CatEat(String name){
System.out.println(name+"在吃鱼!");
}
void MouseEat(String name){
System.out.println(name+"偷吃大米!");
}
void FishEat(String name){
System.out.println(name+"吃虾米!");
}
}
小猫在吃鱼!
老鼠偷吃大米!
鱼吃虾米!
二、接口隔离原则
简而言之,对于A类通过一个接口使用B类的时候,B类所实现的接口应该建立在最小接口上
根据以上类图编写如下代码:
public class Demo02 {
public static void main(String[] args){
}
}
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
//创建A类实现Interface1类
class B implements Interface1{
@Override
public void operation1() {
System.out.println("A operation1");
}
@Override
public void operation2() {
System.out.println("A operation2");
}
@Override
public void operation3() {
System.out.println("A operation3");
}
@Override
public void operation4() {
System.out.println("A operation4");
}
@Override
public void operation5() {
System.out.println("A operation5");
}
}
//创建A类实现B类
class D 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 A{
void function1(Interface1 b){
b.operation1();
}
void function2(Interface1 b){
b.operation2();
}
void function3(Interface1 b){
b.operation3();
}
}
class C{
void function1(Interface1 d){
d.operation1();
}
void function4(Interface1 d){
d.operation4();
}
void function5(Interface1 d){
d.operation5();
}
}
我是个懒人,这一看就能看出问题,A类只用到了B类的三个方法,C类也只用到了D类的三个方法,但是BD两个类却把接口Interface的所有方法都实现了,代码有点过于臃肿
2.1修改思路
把原来的接口拆开来,拆成多个接口,类只需要去实现他会用到的方法接口就行了,在最小程度上实现接口隔离原则
修改代码如下
public class Demo02 {
public static void main(String[] args){
}
}
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("A operation1");
}
@Override
public void operation2() {
System.out.println("A operation2");
}
@Override
public void operation3() {
System.out.println("A operation3");
}
}
class D implements Interface1,Interface3{
@Override
public void operation1() {
System.out.println("B operation1");
}
@Override
public void operation4() {
System.out.println("B operation4");
}
@Override
public void operation5() {
System.out.println("B operation5");
}
}
class A{
void function1(Interface1 b){
b.operation1();
}
void function2(Interface2 b){
b.operation2();
}
void function3(Interface2 b){
b.operation3();
}
}
class C{
void function1(Interface1 d){
d.operation1();
}
void function4(Interface3 d){
d.operation4();
}
void function5(Interface3 d){
d.operation5();
}
}
三、依赖倒转原则
简而言之,在接收具体实现过程时应该交给实现类来完成,但传入参数可以用抽象类和接口来定义,从而设计在调用和传入中间过度一个缓冲层,给代码增加啊伸缩性以及可扩展性
不多bb看代码:
public class Demo03 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
String getInfo(){
return "Hello, Email";
}
}
class Person{
void receive(Email msg){
System.out.println(msg.getInfo());
}
}
在这段通俗易懂的代码中,可以看到Person可以接收email的消息,但问题是现在都是2022年了,我想来一个接收飞鸽传书的方式,是不是得在重写一个方法,而且在传入参数直接传入了一个对象,这不是一个很好的选择(虽然我以前经常这么干)
3.1解决思路
根据上述问题,通过依赖倒转原则可以轻松得解决,甚至还很舒服,我看完只能说“66666666666666666666”
第一步我先创建一个Receiver的接口,我不管你是Email、微信、QQ、飞鸽传书哪怕是漂流瓶都得实现这个接口,然后在Person类里面的receive方法的参数msg类型改为Receiver,这样做的好处是:
- 我可以传递任何实现接口Receiver的类
- 如果我想增加一个facebook的消息只需要在写一个类去实现Receiver即可
- 我觉得很有逼格
public class Demo03 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new FlyingPigeon());
person.receive(new QQ());
}
}
interface Receiver{
String getInfo();
}
class Email implements Receiver{
public String getInfo(){
return "Hello, Email";
}
}
class FlyingPigeon implements Receiver{
public String getInfo(){
return "Hello, I'm good boy";
}
}
class QQ implements Receiver{
public String getInfo(){
return "Hello, You are my friend.";
}
}
class Person{
void receive(Receiver msg){
System.out.println(msg.getInfo());
}
}
Hello, Email
Hello, I'm good boy
Hello, You are my friend.
四、里氏替换原则
简而言之,子类尽量少重写父类的方法,一定要写可以通过聚合、组合、依赖来解决
public class Demo04 {
public static void main(String[] args) {
B b = new B();
//在这里我以为我通过B类对象能够调用到A类的fun1
System.out.println("11-3="+b.fun1(11, 3));
System.out.println("1-8="+b.fun1(1, 8));
}
}
class A{
public int fun1(int a,int b){
return a-b;
}
}
class B extends A{
//试图重载A类方法 但我不知道重载了
public int fun1(int a,int b){
return a+b;
}
public int fun2(int a,int b){
return fun1(a,b)+9;
}
}
在这段代码中有一个逻辑性的错误,在主方法中本意是通过B类对象去调用A类方法,但在B类中错误的将fun1的方法重写了,导致代码上的逻辑性错误,这一错误看似是一个很刻意的,但在实际开发中,很难会提示到自己从写了父类的方法然后在主函数中又错误的调用。
4.1解决思路
在视频课里面,这个例子举得多少有点牵强,并且创造了一个base类,让AB类去继承,但在一个没有更加基础属性和方法的情况下base类是可以不加的,直接在B类中调用A类的方法通过组合来解决这个问题(个人理解),如果后续还要增加一个两个类公共的东西可以创建base类
public class Demo04 {
public static void main(String[] args) {
B b = new B();
System.out.println("11-3="+b.fun3(11, 3));
System.out.println("1-8="+b.fun3(1, 8));
}
}
class A{
public int fun1(int a,int b){
return a-b;
}
}
class B{
private A a = new A();
public int fun1(int a,int b){
return a+b;
}
public int fun2(int a,int b){
return fun1(a,b)+9;
}
public int fun3(int a,int b){
return this.a.fun1(a, b);
}
}
五、开闭原则
对扩展功能开放,对修改功能关闭
public class Demo05 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Rectangle());
}
}
class GraphicEditor{
public void drawShape(Shape s){
if(s.m_type == 1){
this.drawRect(s);
}else if(s.m_type == 2){
this.drawCircle(s);
}
}
public void drawRect(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;
}
}
就不输出了,在上述代码中创建了一个画图形的类(GraphicEditor),里面有一个方法drawShape,根据传递进去的s的m_type来判断到底是画矩形还是画圆,如果我现在想增加一个画三角形的代码需要更改几处地方???
5.1解决思路
其实就是想打算在不修改GraphicEditor的情况下增加功能,这样我就可以对使用方关闭修改,在另外一处开辟一个拓展的功能,这里用到的跟依赖倒转原则很像,创建一个抽象类作为缓冲层,对外扩展可以新增实现这个抽象类的实现类
public class Demo05 {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Rectangle());
}
}
class GraphicEditor{
public void drawShape(Shape s){
s.draw();
}
}
abstract class Shape{
int m_type;
abstract void draw();
}
class Rectangle extends Shape{
Rectangle(){
super.m_type = 1;
}
@Override
void draw() {
System.out.println("矩形");
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
@Override
void draw() {
System.out.println("圆");
}
}
六、迪米特法则
也叫最少知道法则,陌生的类尽量不要以局部变量的形式出现,对依赖的类知道的越少越好
七、合成复用原则
能用合成/聚合就不用继承