山东大学软件学院面向对象期末复习

面向对象

文章目录

  • 面向对象
    • 04 类
      • 封装
      • 接口 抽象类
    • 05 消息,实例化,静态变量方法
      • 消息
      • 动/静态类型语言
      • 对象创建
        • 类及实例具有下面特征
        • 对象数组的创建
      • 静态数据成员
      • 构造函数
    • 06_0 继承
      • 继承是向下传递的
      • JAVA为什么不支持多重继承
      • 继承的形式
        • 特殊化继承
          • 替换原则
        • 规范化继承
        • 构造继承
        • 泛化继承
        • 扩展继承
        • 限制继承
        • 变体继承
        • 合并继承
    • 06_1 多态,动态静态,替换
      • 静态类,动态类
      • 重写
        • 向下造型
      • 多态变量
      • 重载
      • 重定义
      • 重写和重载的区别
      • 分配方案
    • 06_2 多态
      • 多态变量
      • 多态的运行机制
      • 方法的动态绑定过程
    • 06_3 多重继承
      • 多重继承(了解)
        • 多重继承中存在的问题:名字冲突
        • 消除二义性的方法
    • 07 软件复用
      • 继承和组合的比较
    • 设计原则
      • 开闭原则OCP: Open-Closed Principle
      • 里氏代换原则LSP: Liskov Substitution Principle
      • 依赖倒置原则DIP: Dependency Inversion Principle
        • 通过构造函数传递依赖对象
        • 通过setter方法传递依赖对象
        • 接口声明实现依赖对象
      • 接口隔离原则ISP: Interface Segregation Principle
      • 组合优先原则
      • 单一职责原则(SRP)
      • 针对接口编程
    • 设计模式
      • 简单工厂
      • 工厂方法
      • 单例模式
      • 桥梁模式
      • 装饰者模式
      • 适配器模式
      • 观察者模式
      • 策略模式

本文章基于面向对象课程的PPT整理而成,只整理了部分重点内容,以供期末复习时使用,愿大家期末考出好成绩

04 类

封装

具体地,封装就是指利用抽象数据类型将数据和基于数据的操作封装在一起

数据被保护在抽象数据类型的内部

系统的其他部分只有通过包裹在数据外面的被授权的操作,才能够与这个抽象数据类型交流和交互。

封装特性把类内的数据保护得很严密,模块与模块间仅通过严格控制的界面进行交互

使它们之间耦合和交叉大大减少,从而降低了开发过程的复杂性,提高了效率和质量,减少了可能的错误

接口 抽象类

接口的结构和抽象类非常相似,它也具有数据成员与抽象方法,但它与抽象类有两点不同:

  • 接口的数据成员必须初始化。
  • 接口里的方法全部都是abstract。

不相同的地方:

  • 抽象类是对类的抽象。而接口只是一个行为的规范或规定。
  • 接口不具备任何继承的特点。它仅仅是承诺该完成的方法。
  • 类可以实现多个接口,但只能继承一个父类。
  • 抽象类可以包含方法的实现(非抽象方法),而接口中所有的方法都没有实现。

05 消息,实例化,静态变量方法

消息

考虑对象A向对象B发送消息,也可以看成对象A向对象B请求服务

  • 对象A要明确知道对象B提供什么样的服务
  • 根据请求服务的不同,对象A可能需要给对象B一些额外的信息,以使对象B明确知道如何处理该服务
  • 对象B可将最终的执行结果以报告形式反馈回去

动/静态类型语言

动态类型语言(Dynamically Typed Language ):变量看作名称标识,类型和数值联系在一起

  • 类型的检查是在运行时做的;
  • 一般在变量使用之前不需要声明变量类型,而变量的类型通常是由被赋的值的类型决定。
  • 优点是方便阅读,不需要写非常多的类型相关的代码
  • 缺点是不方便调试,命名不规范时会造成读不懂,不利于理解等

静态类型语言(Statically Typed Language ):类型和变量联系在一起

  • 类型的检查是在编译时做的.
  • 在编译时,便需要确定类型的语言。即写程序时需要明确声明变量类型。
  • 优点在于其结构非常规范,便于调试,方便类型安全;
  • 缺点是为此需要写更多的类型相关代码,导致不便于阅读、不清晰明了

对象创建

类及实例具有下面特征
  • 同一个类的不同实例具有相同的数据结构,承受的是同一方法集合所定义的操作,因而具有相同的行为
  • 同一个类的不同实例可以持有不同的值,因而可以有不同的状态
  • 实例的初始状态(初值)可以在实例化中确定
对象数组的创建

C++:结合

  • 对象使用缺省构造函数来初始化。
  • 数组由对象组成,每个对象则使用缺省(即无参数)构造函数来进行初始化
  • PlayingCard *cardArray[52];

Java:new仅创建数组。数组包含的对象必须独立创建。

PlayingCard cardArray[ ] = new PlayingCard[13];
for (int i = 0; i < 13; i++)cardArray[i] = new PlayingCard(Spade,i+1);

静态数据成员

利用static关键字修饰的数据成员称为静态数据成员/静态数据字段/静态变量/类成员,也称为静态成员(全局成员)。

利用static关键字修饰的成员方法为类方法/静态成员方法,类方法可以由类直接调用

静态成员不需要实例化就存在,而非静态成员是实例化后才有的成员,在没有实例化之前非静态成员并不存在。因此可以利用仅仅在某一时刻存在的对象访问普遍存在的对象;而不能用一个普遍存在的对象访问仅仅在某一时刻存在的对象。

对象本身不对共享字段初始化。内存管理器自动将共享数据初始化为某特定值,每个实例去测试该特定值。第一个进行测试的做初始化。

Java中,静态数据字段的初始化是在加载类时,执行静态块来完成。

静态数据字段(静态变量)与静态方法都是在类从磁盘加载至内存后被创建的,与类同时存在,同时消亡。

构造函数

缺省构造方法:没有参数的构造方法。如果程序员不提供任何来构造方法,则编译程序自动提供一个构造方法;只要程序员提供了一个构造方法,系统不再提供缺省构造方法

编译程序提供的缺省构造方法只做一件事:调用父类的缺省构造方法

06_0 继承

继承是向下传递的

子类所具有的数据和行为总是作为与其相关的父类的属性的扩展( extension)(即更大的集合)。

子类具有父类的所有属性以及其他属性。

继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性 。

派生类可以覆盖从基类继承来的行为。

JAVA为什么不支持多重继承

因为当一个类同时继承两个父类时,两个父类中有相同的功能,则子类对象调用该功能时,无法确定运行的是哪一个?

但是java支持多级继承。A继承B B继承C C继承D。

多级继承的出现,就有了继承体系。

体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。

继承的形式

特殊化继承

很多情况下,都是为了特殊化才使用继承。

在这种形式下,新类是基类的一种特定类型,它能满足基类的所有规范。

用这种方式创建的总是子类型,并明显符合可替换性原则。

替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能

子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

子类中可以增加自己特有的方法。

当子类的方法重写父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

规范化继承

基类中既有已实现的方法,也有只定义了方法接口、留待派生类去实现的方法。

派生类只是实现了那些定义在基类却又没有实现的方法。

派生类并没有重新定义已有的类型,而是去实现一个未完成的抽象规范。

构造继承

子类使用父类提供的行为,但并不是父类的子类型

子类通过继承父类就可以实现其需要的行为,只需要对方法名称、方法参数等进行修改

泛化继承

子类修改或改写父类的某些方法

扩展继承

子类只是往父类中添加新行为,并不修改从父类继承来的任何属性

由于父类的功能仍然可以使用,而且没有被修改,因此扩展继承并不违反可替换性原则,用这种方式构建的子类还是子类型

限制继承

如果子类的行为比父类的少或更严格,就出现了限制继承

变体继承

子类和父类之间都是对方的变体,可以任意选择两个之间的父子关系

例如,用为控制鼠标的代码与用来控制图形输入板的代码几乎完全相同。在概念上没有理由让一个类作为另外一个类的父类,因此可以选择任何一个类作为另外一个类的父类

合并继承

可以通过合并两个或者更多的抽象特性来形成新的抽象。

一个类可以继承自多个基类的能力被称为多重继承 。

06_1 多态,动态静态,替换

静态类,动态类

变量的静态类是指用于声明变量的类。静态类在编译时就确定下来,并且再也不会改变。类型由声明该变量时使用的类型决定。

变量的动态类指与变量所表示的当前数值相关的类。动态类在程序的执行过程中,当对变量赋新值时可以改变。类型由实际赋给该变量的对象决定。

如果变量的动态类和静态类的类型不一致,会出现所谓的多态。

多态的四种形式:重载,改写,多态变量,泛型

重写

在继承情况下,子类中定义了与其基类中方法具有相同名称、相同类型签名(相同返回值类型或兼容类型和相同参数类型)的方法,但重新编写了方法体

两种不同的关于改写/覆盖/重写的解释方式:

  1. 代替(replacement):在程序执行时,实现代替的方法完全覆盖父类的方法。即,当操作子类实例时,父类的代码完全不会执行
  2. 改进(refinement):实现改进的方法将继承自父类的方法的执行作为其行为的一部分。这样父类的行为得以保留且扩充。
向下造型

把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要显式转型。

做出数值是否属于指定类的决定之后,通常下一步就是将这一数值的类型由父类转换为子类。

这一过程称为向下造型,或者反多态,因为这一操作所产生的效果恰好与多态赋值的效果相反。

class Person { public void fun1() { System.out.println(1.Person{fun1()}); } public void fun2() { System.out.println(2.Person{fun2()}); }
}  
//继承了父类Person,自然继承了方法fun1、fun2 
class Student extends Person { public void fun1() {//覆写了父类中的方法fun1()System.out.println(3.Student{fun1()});  } public void fun3() { System.out.println(4.Student{fun3()}); } 
}  
class TestJavaDemo2 { public static void main(String[] args) { Person p = new Person(); //父类对象由自身实例化 Student s = (Student)p; //将p对象向下转型 p.fun1();  p.fun2(); 
}
//Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to Student编译无错,但是运行出错!class TestJavaDemo2 { public static void main(String[] args) { Person p = new Student();   	//让父类知道有这么一个子类 Student s = (Student)p; //将p对象向下转型 p.fun1();  p.fun2(); p.fun3();//编译错误 ,用父类引用调用父类不存在的方法 } 
}

多态变量

如果方法所执行的消息绑定是由最近赋值给变量的数值的类型来决定的,那么就称这个变量是多态的。

Java,Smalltalk等语言变量都可以是多态的。

Animal pet;
pet = new Dog();
pet.speak();pet = new bird();
pet.speak();

重载

同一个类定义中有多个同名的方法,但有不同的形参,而且每个方法有不同的方法体,调用时根据形参的个数、顺序和类型来决定调用的是哪个方法

重载是在编译时执行的,而改写是在运行时选择的。

对于 Java 语言,如果两个或者更多的方法具有相同的名称和相同的参数数目,则编译器将使用下面的算法来确定如何匹配

  1. 找到所有可能进行调用的方法,亦即,各个参数(实参)可以合法地赋值给各个参数类型(形参)的所有方法。如果找到一个在调用时可以完全匹配所使用的参数类型的方法,那么就执行这个方法。
  2. 对于第一步所产生的集合中的方法,两两进行比较,如果一个方法的所有参数类型都可以赋值给另外一个方法,那么就将第二个方法从集合中移走。重复以上操作,直至无法实现进一步的缩减为止。
  3. 如果只剩下一个方法,那么这个方法就非常明确了,调用这个方法即可。如果剩余的方法不止一个,那么调用就产生歧义了,此时编译器将报告错误。

重定义

当子类定义了一个与父类具有相同名称但类型签名不同的方法时,发生重定义。

类型签名的变化是重定义区别于改写/覆盖/重写的主要依据。

重写和重载的区别

方法的改写/覆盖是子类和父类之间的关系,而重载一般是同一类内部多个方法间的关系

方法的改写/覆盖一般是两个方法间的,而重载时可能有多个重载方法

改写/覆盖的方法有相同的方法名和形参表,而重载的方法只能有相同的方法名,不能有相同的形参表

改写/覆盖时区分方法的是根据被调用方法的对象,而重载是根据参数来决定调用的是哪个方法

用final修饰的方法是不能被子类覆盖的,只能被重载

重写方法不能使用比被重写的方法更严格的访问权限,重载可以有不同的访问修饰符

重写子类方法不能抛出比父类方法更多的异常。子类方法抛出的异常必须和父类方法抛出的异常相同,或者子类方法抛出的异常类是父类抛出的异常类的子类,重载可以抛出不同的异常

分配方案

最小静态空间分配:只分配基类所需的存储空间。

最大静态空间分配:无论基类还是派生类,都分配可用于所有合法数值的最大的存储空间。

动态内存分配:只分配用于保存一个指针所需的存储空间。在运行时分配对象所需的存储空间,同时将指针设为相应的合适值(地址)。

06_2 多态

多态变量

多态变量是指可以引用多种对象类型的变量。

这种变量在程序执行过程可以包含不同类型的数值。

对于动态类型语言,所有的变量都可能是多态的。对于静态类型语言,多态变量则是替换原则的具体表现。

多态的运行机制

Java多态机制是基于“方法绑定(binding)”,就是建立method call(方法调用)和method body(方法本体)的关联。如果绑定动作发生于程序执行前(由编译器和连接器完成),称为“先期绑定”。对于面向过程的语言它们没有其他选择,一定是先期绑定。比如C编译器只有一种method call,就是先期绑定。(C++有先期联编和后期联编)

当有多态的情况时,解决方案便是所谓的后期绑定(late binding):绑定动作将在执行期才根据对象型别而进行。后期绑定也被称为执行期绑定(run-time binding)或动态绑定(dynamic binding)。

Java的所有方法,只有final,static,private和构造方法是前期绑定,其他都使用后期绑定。 将方法声明为final型可以有效防止他人覆写该函数。但是或许更重要的是,这么做可以“关闭”动态绑定。或者说,这么做便是告诉编译器:动态绑定是不需要的。于是编译器可以产生效率较佳的程序代码。

方法的动态绑定过程

  1. 编译器检查对象的声明类型和方法名。假设我们调用x.f(args)方法,并且x已经被声明为C类的变量,那么编译器会列举出C类中所有的名称为f的方法和从C类的超类继承过来的f方法

  2. 接下来编译器检查方法调用中提供的参数类型。如果在所有名称为f 的方法中有一个参数类型和调用提供的参数类型最为匹配,那么就确定调用这个方法( 重载解析)

  3. 当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同x所指向的对象的实际类型相匹配的方法版本。假设实际类型为D(C的子类),如果D类定义了f(args)那么该方法被调用,否则就在D的超类中搜寻方法f(args),依次类推

注:

  • 第一步和第二步在编译时期完成,它们确定了可能的方法调用集合和具体的签名,但这只是静态分析的结果。
  • 第三步在运行时期完成,它根据对象的实际类型确定最终调用哪个方法实现。这是动态绑定的核心,它补充了静态绑定的不足,允许程序在运行时表现出不同的行为,具体取决于对象的实际类型。
public class  Bird{public void  fly(Bird   p) {System.out.println(Bird fly with Bird);}
}
public class Eagle extends Bird {public void fly(Bird   p){System.out.println(Eagle fly with Bird!);}public void fly(Eagle e) {System.out.println(Eagle fly with Eagle!);}
}
Bird p1 = new Bird () ; 
Bird  p2 = new Eagle () ; 
Eagle p3 = new Eagle () ; 
p1.fly(  p1  ) ;
p1.fly(  p2  ) ;
p1.fly(  p3  ) ;
p2.fly(  p1  ) ;
p2.fly(  p2  ) ;
p2.fly(  p3  ) ;
p3.fly(  p1  ) ;
p3.fly(  p2  ) ;
p3.fly(  p3  ) ;Bird fly with Bird
Bird fly with Bird
Bird fly with Bird    
Eagle fly with Bird
Eagle fly with Bird    
Eagle fly with Bird
Eagle fly with Bird
Eagle fly with Bird
Eagle fly with Eagle    
class A {public String show(D obj){return ("A and D");}public String show(A obj){return ("A and A");}
}
class B extends A{public String show(B obj) {return ("B and B");}public String show(A obj) {return ("B and A");}
}
class C extends B...{} 
class D extends B...{} A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println(a1.show(b));System.out.println(a1.show(c));System.out.println(a1.show(d));System.out.println(a2.show(b));System.out.println(a2.show(c));System.out.println(a2.show(d));System.out.println(b.show(b));System.out.println(b.show(c));System.out.println(b.show(d));    ⑨   ①   A and AA and AA and DB and AB and AA and DB and BB and BA and D    

06_3 多重继承

多重继承(了解)

一个对象可以有两个或更多不同的父类,并可以继承每个父类的数据和行为。

派生的分类对每个父类仍然符合“是一个”规则,或“作为一个”关系。

多重继承中存在的问题:名字冲突

在多重继承下,若多个基类具有相同的成员名,可能造成对基类中该成员的访问出现不是唯一的情况,则称为对基类成员访问的二义性。

int main()
{DeviceNew  device(0.7,3,false,10,250,80);cout<<The weight of the device:<<device.getWeight();device.showPower();device.showProperty();return 0;
}
class DeviceNewpublic Device1,public Device2 {}
//Device1和Device2中都定义了showPower()方法
消除二义性的方法
  1. 使用相应的类名来标识

device.Device1::showPower();

  1. 在派生类中重定义有名称冲突的成员

07 软件复用

继承和组合的比较

继承描述的是一种一般性和特殊性的关系,使用继承可创建已存在类的特殊版本。

组合描述的是一种组成关系,使用组合可用已存在的类组装新的类。

动态与静态复用:

  • 继承是在编译时刻静态定义的,即是静态复用,在编译后子类和父类的关系就已经确定了。
  • 组合中类之间的关系是在运行时候才确定的,即在对象没有创建运行前,整体类是不会知道自己将持有特定接口下的哪个实现类。在扩展方面组合比继承更具有广泛性。

封装性:

  • 继承中父类定义了子类的部分实现,而子类中又会重写这些实现,修改父类的实现,设计模式中认为这是一种破坏了父类的封装性的表现。这个结构导致结果是父类实现的任何变化,必然导致子类的改变。
  • 组合有助于保持每个类被封装,并被集中在单个任务上(类设计的单一原则)。这样类的层次结构不会扩大,一般不会出现不可控的庞然大类。而类的继承就可能出现这些问题,所以一般编码规范都要求类的层次结构不要超过3层。组合是大型系统软件实现即插即用时的首选方式。
组 合 关 系继 承 关 系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
优点:具有较好的可扩展性缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象缺点:不支持动态继承。在运行时,子类无法选择不同的父类
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口缺点:子类不能改变父类的接口
缺点:整体类不能自动获得和局部类同样的接口优点:子类能自动继承父类的接口
缺点:创建整体类的对象时,需要创建所有局部类的对象优点:创建子类的对象时,无须创建父类的对象

设计原则

开闭原则OCP: Open-Closed Principle

软件组成实体应该是对扩展开放的(可扩展的),但是对修改是关闭的。

可给系统定义一个一劳永逸,不再更改的抽象设计,此设计允许有无穷无尽的行为在实现层被实现。抽象层预见所有扩展。

class Part {protected double basePrice;private PricePolicy pricePolicy;public void setPricePolicy(PricePolicy policy) {pricePolicy = policy;}public void setPrice(double price) {basePrice = price;}public double getPrice() {if (pricePolicy == null)return basePrice;elsereturn pricePolicy.getPrice(basePrice);}
}class PricePolicy {public double getPrice(double basePrice) {return basePrice;}
}
class Sale extends PricePolicy {private double discount;public void setDiscount(double discount) {this.discount = discount;}public double getPrice(double basePrice) {return basePrice * discount;}
}public class TestOCP {public static void main(String[] args) {// TODO Auto-generated method stubPart p1 = new Memory();p1.setPrice(599);Part p2 = new Disk();p2.setPrice(499);Sale sale = new Sale();sale.setDiscount(0.75);p2.setPricePolicy(sale);Part[] com = { p1, p2 };System.out.println(totalprice(com));}
}

里氏代换原则LSP: Liskov Substitution Principle

一个软件如果使用的是一个父类的话,如果把该父类换成子类,它不能察觉出父类对象和子类对象的区别

凡是父类适用的地方子类也适用

继承只有满足里氏代换原则才是合理的

子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。

子类中可以增加自己特有的方法。

当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

依赖倒置原则DIP: Dependency Inversion Principle

抽象(抽象类或接口)不应该依赖于细节(具体实现类)

细节(具体实现类)应该依赖抽象

抽象:即抽象类或接口,两者是不能够实例化的。

细节:即具体的实现类,实现接口或者继承抽象类所产生的类,两者可以通过关键字new直接被实例化。

而依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。

通过构造函数传递依赖对象
public interface IDriver {//是司机就应该会驾驶汽车public void drive();
}
public class Driver implements IDriver{private ICar car;
//构造函数注入public Driver(ICar _car){this.car = _car;}//司机的主要职责就是驾驶汽车public void drive(){this.car.run();}
}
通过setter方法传递依赖对象
public interface IDriver {
//车辆型号public void setCar(ICar car);
//是司机就应该会驾驶汽车public void drive();
}
public class Driver implements IDriver{private ICar car;public void setCar(ICar car){this.car = car;}
//司机的主要职责就是驾驶汽车public void drive(){this.car.run();}
}public interface ICar {
//是汽车就应该能跑public void run();}
public class Benz implements ICar{public void run(){System.out.println("奔驰汽车开始运行...");}
}public class Client {public static void main(..) {IDriver zhangSan = new Driver();ICar benz = new Benz();
//张三开奔驰车zhangsan.setCar(benz);}
}
接口声明实现依赖对象
public interface ICar {
//是汽车就应该能跑public void run();}
public class Benz implements ICar{
//汽车肯定会跑public void run(){System.out.println("奔驰汽车开始运行...");}
}public interface IDriver {
//是司机就应该会驾驶汽车public void drive(ICar car);
}
public class Driver implements IDriver{
//司机的主要职责就是驾驶汽车public void drive(ICar car){car.run();}
}
public class Client {public static void main(String[] args) {IDriver zhangSan = new Driver();ICar benz = new Benz();
//张三开奔驰车zhangSan.drive(benz);    }
}

司机类和奔驰车类之间是一个紧耦合的关系,其导致的结果就是系统的可维护性大大降低。

代码可读性降低,两个相似的类需要阅读两个文件。

系统稳定性降低,这里只是增加了一个车类就需要修改司机类,这不是稳定性,这是易变性。被依赖者的变更让依赖者来承担修改的成本。

接口隔离原则ISP: Interface Segregation Principle

建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。

我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

使用多个专门的接口比使用单一的总接口好(客户端不应该依赖它不需要的接口)。

组合优先原则

当需要应对变化的时候,应该首先使用组合的方式,而不是继承

单一职责原则(SRP)

一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

针对接口编程

要针对接口编程不要针对实现编程

针对接口编程”的一些建议

变量、参数、返回值等应声明为抽象类

不要继承非抽象类

不要重载父类的非抽象方法

设计模式

简单工厂

/** 接口的定义,该接口可以通过简单工厂来创建*/   
public interface Api {  /**  * 示意,具体的功能方法的定义   */  public void operation(String s);  
} 
/*** 接口的具体实现对象A  . */  
public class ImplA implements Api{  public void operation(String s) {  //实现功能的代码,示意一下  System.out.println("ImplA s=="+s);  }  
} /**  * 工厂类,用来创造Api对象  */  
public class Factory {  /**      * 具体的创造Api对象的方法 * @param condition 示意,从外部传入的选择条件 ; @return 创造好的Api对象      */  public static Api createApi(int condition){  //根据某些条件去选择究竟创建哪一个具体的实现对象, 这些条件可以从外部传入,也可以从其它途径获取。如果只有一个实现,可省略条件。  Api api = null;  if(condition == 1){  api = new ImplA();  }else if(condition == 2){  api = new ImplB();   }  return api;   }  
} /**  * 客户端,使用Api接口  */  
public class Client {  public static void main(String[] args) {  //通过简单工厂来获取接口对象  Api api = Factory.createApi(1);  api.operation("正在使用简单工厂");  }  
} 

缺点:由于是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节。

工厂方法

不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。

/* 工厂方法所创建的对象的接口*/
abstract class Product {//可以定义Product的属性和方法
}
/** * 具体的Product对象 */
public class ConcreteProduct extends Product {//实现Product要求的方法
}/*** 客户端使用Facotry对象的情况下,Factory的基本实现结构*/
abstract class Factory {  public abstract Product factoryMethod();  
} /**具体的创建器实现对象*/
class ConcreteFactory extends Factory {  public Product factoryMethod() {  return new ConcreteProduct();  }  
} /*客户端程序*/
Factory factory;
factory = new ConcreteFactory(); 
//可通过配置文件实现
Product product;
product = factory.factoryMethod();
……

在这里插入图片描述

优点

  • 封装了创建具体对象的工作
  • 使得客户代码“针对接口编程”,保持对变化的“关闭”(产品)

缺点

  • 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类

单例模式

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

// 懒汉式
public class Singleton {private static Singleton uniqueInstance;// other useful instance variables hereprivate Singleton() {}public static Singleton getInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;}// other useful methods here
}// 饿汉式
public class Singleton {// 直接为该单例类创建一个实例对象 	private static Singleton uniqueInstance = new Singleton();private Singleton() {}// 直接返回唯一的单例对象public static Singleton getInstance() {return uniqueInstance;}
}//双重检查加锁
public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getInstance() {if (uniqueInstance == null) {synchronized (Singleton.class) {if (uniqueInstance == null) {uniqueInstance = new Singleton();}}}return uniqueInstance;}
}
//被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确地处理该变量。

桥梁模式

将抽象部分与它的实现部分分离,使它们都可以独立地变化

//抽象化角色类
public abstract class Abstraction {protected Implementor impl;public Abstraction(Implementor impl){this.impl = impl;}//示例方法public abstract void operation();
}//修正抽象化角色类
public class RefinedAbstraction extends Abstraction {public RefinedAbstraction(Implementor impl) {super(impl);}public void operation(){impl.operationImpl();}//其他的操作方法public void otherOperation(){  }
}//实现化角色类
public abstract class Implementor {/*** 示例方法,实现抽象部分需要的某些具体功能*/public abstract void operationImpl();
}
//具体实现化角色类
public class ConcreteImplementorA extends Implementor {public void operationImpl() {//具体操作}
}
public class ConcreteImplementorB extends Implementor {public void operationImpl() {//具体操作}
}

在这里插入图片描述

装饰者模式

抽象构件(Component)角色:组件对象的接口,可以给这些对象动态地添加职责

具体构件(Concrete Component)角色:实现组件对象接口,通常就是被装饰者装饰的原始对象,也就是可以给这个对象添加职责。

装饰者(Decorator)角色:所有装饰者的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。

具体装饰者(Concrete Decorator)角色:实际的装饰者对象,实现具体要向被装饰对象添加的功能。

public abstract class Beverage {protected String description; public String getDescription()   { return description;     }public abstract double cost();
}
public class DarkRoast extends Beverage {public DarkRoast() {description = "Dark Roast Coffee";}public double cost() {return .99;}
}public abstract class CondimentDecorator  extends Beverage {protected Beverage beverage;}public class Mocha extends CondimentDecorator {public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return .20 + beverage.cost();}
}public class StarbuzzCoffee {public static void main(String args[]) {Beverage beverage = new Decaf();System.out.println(beverage.getDescription() + " $" + beverage.cost());Beverage beverage1 = new Decaf();beverage1 = new Milk(beverage1);beverage1 = new Mocha(beverage1);System.out.println(beverage1.getDescription() + " $" + beverage1.cost());Beverage beverage2 = new DarkRoast();beverage2 = new Mocha(beverage2);beverage2 = new Milk(beverage2);System.out.println(beverage2.getDescription() + " $" + beverage2.cost());}
}

在这里插入图片描述

继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能。(继承在扩展功能是静态的,必须在编译时就确定好,而使用装饰者可以在运行时决定,装饰者也建立在继承的基础之上的)

通过使用不同装饰类以及这些类的排列组合,可以实现不同的效果。

松耦合

符合开闭原则

适配器模式

将一个类的接口转换成客户希望的另外一个接口使得原来由于接口不兼容而不能一起工作的那些类可以一起工作

实现方式:两个类(接口)A,B,定义一个类C来实现B的接口,然后在其内部实现时,转调A类已经实现的功能,这样可以通过对象组合的方式,既复用了A的功能,同时又在接口上满足了B调用的要求

// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class Adaptee {public void specificRequest() {System.out.println("被适配类具有 特殊功能...");}
}// 目标接口,或称为标准接口
interface Target {public void request();
}// 具体目标类,只提供普通功能
class ConcreteTarget implements Target {public void request() {System.out.println("普通类 具有 普通功能...");}
}// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target{public void request() {super.specificRequest();}
}// 测试类
public class Client {public static void main(String[] args) {
// 使用普通功能类Target   concreteTarget = new ConcreteTarget();concreteTarget.request();
// 使用特殊功能类,即适配类Target adapter = new Adapter();adapter.request();}
}

在这里插入图片描述

观察者模式

观察者模式把多个订阅者称为观察者:Observer;多个观察者观察的对象被称为目标:Subject。

一个目标可以有任意多个观察者对象,一旦目标的状态发生了改变,所有注册的观察者都会得到通知,然后各个观察者会对通知作出相应的响应,执行相应的业务功能处理,并使自己的状态和目标对象的状态保持一致。

import java.util.*;
public abstract class Subject {//定义一个观察者集合用于存储所有观察者对象protected ArrayList<Observer>  observers= new ArrayList();//注册方法,用于向观察者集合中增加一个观察者public void attach(Observer observer) {observers.add(observer);}//注销方法,用于在观察者集合中删除一个观察者public void detach(Observer observer) {observers.remove(observer);}//声明抽象通知方法public abstract void notifyAllObservers();
}public class ConcreteSubject extends Subject {//实现通知方法public void notifyAllObservers() {//遍历观察者集合,调用每一个观察者的响应方法for(Object obs:observers) {((Observer)obs).update();}}	
}public interface Observer {//声明响应方法public void update();
}public class ConcreteObserver implements Observer {//实现响应方法public void update() {//具体响应代码}
}//客户端代码:
Subject subject=new ConcreteSubject();
Observer observer=new ConcreteObserver();
subject.attach(observer);
subject.notifyAllObservers();

在这里插入图片描述

当修改目标对象的状态的时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法,其实就相当于联动调用这些观察者的方法。

这个联动还是动态的,可以通过注册和取消注册来控制观察者,因而可以在程序运行期间,通过动态地控制观察者,来变相地实现添加和删除某些功能处理,这些功能就是观察者在update的时候执行的功能。

目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动过来。

策略模式

策略模式属于对象的行为模式。

其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

策略模式使得算法可以在不影响到客户端的情况下发生变化。

public class Context {//持有一个具体策略的对象private Strategy strategy;/*** 构造函数,传入一个具体策略对象* @param strategy    具体策略对象 */public Context(Strategy strategy){this.strategy = strategy;}/*** 策略方法*/public void contextInterface(){strategy.strategyInterface();} 
}//抽象策略类
public interface Strategy {
//* 策略方法public void strategyInterface();
}
//具体策略类
public class ConcreteStrategyA implements Strategy {public void strategyInterface() {//相关的业务}
}
public class ConcreteStrategyB implements Strategy {public void strategyInterface() {//相关的业务}
}

组合优先:尽量用组合,而不是继承

开-闭原则:增加新功能,要做到只增加新代码,而不改动老代码

封装可变性:把变化的代码从不变的代码中分离出来,限制变化的影响范围

针对接口编程而不是具体类(定义了策略接口)
实现添加和删除某些功能处理,这些功能就是观察者在update的时候执行的功能。

目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动过来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915410.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915410.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

让 Windows 用上 macOS 的系统下载与保姆级使用教程

模拟苹果桌面软件下载&#xff1a;https://xpan.com.cn/s/8NFAGT 还记得 Windows 11刚发布时&#xff0c;很多人就说“果里果气"的&#xff0c;但界面确实做的漂亮。 不知道现在有多少小伙伴正用着macOS&#xff0c;不过我敢确定&#xff0c;喜欢macOS的人绝对不少&#…

嵌入式硬件篇---继电器

继电器是一种通过小电流控制大电流的电磁开关&#xff0c;广泛应用于自动化控制、电力系统和电子设备中。以下从工作原理、应用场景和电路特点三个方面详细介绍&#xff1a;一、工作原理继电器本质是电磁控制的机械式开关&#xff0c;核心部件包括&#xff1a;线圈&#xff08;…

鸿蒙网络编程系列58-仓颉版TLS数字证书查看及验签示例

1. TLS数字证书验签简介 数字证书的签名验证是网络编程中一个重要的功能&#xff0c;它保证了数字证书是由可信任的签发方签署的&#xff0c;在此基础上&#xff0c;我们才可以信任该证书&#xff0c;进而信任基于该证书建立的安全通道&#xff0c;所以说&#xff0c;数字证书…

【React Native】安装配置 Expo Router

过去开发React Native&#xff0c;所使用的路由都是React Navigation。但是这个东西使用起来非常困难&#xff0c;配置无比繁琐。Expo&#xff0c;为了简化操作&#xff0c;就基于React Navigation开发了Expo Router。 Expo Router用起来就要简单的多了&#xff0c;配置也相对…

美国VPS服务器Linux内核参数调优的实践与验证

美国vps服务器Linux内核参数调优的实践与验证在云计算和虚拟化技术日益普及的今天&#xff0c;美国VPS服务器因其稳定的网络环境和优越的性价比&#xff0c;成为众多企业和开发者的首选。Linux内核参数的默认配置往往无法充分发挥VPS的性能潜力。本文将深入探讨美国VPS服务器上…

在Vscode中使用Kimi K2模型:实践指南,三分钟生成个小游戏

Kimi K2是一款基于多专家&#xff08;MoE&#xff09;架构的强大代码与代理能力基础模型。本文将通过在VS Code及其扩展Cline和RooCode中的实际应用&#xff0c;详细说明如何使用Kimi K2-0711-preview模型。不得不说kimi这次的K2模型就是强大&#xff0c;在vscode中配置使用体验…

基于SpringBoot+Uniapp球场预约小程序(腾讯地图API、Echarts图形化分析、二维码识别)

“ &#x1f388;系统亮点&#xff1a;腾讯地图API、Echarts图形化分析、二维码识别”01系统开发工具与环境搭建前后端分离架构 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17前端&#xff1a; 技术&#xff1a;框架Vue.js&#xff1b;UI库&#xff1a;…

windows + phpstorm 2024 + phpstudy 8 + php7.3 + thinkphp6 配置xdebug调试

windows phpstorm 2024 phpstudy 8 php7.3 thinkphp6 配置xdebug调试 下载配置phpstudyPhp.ini配置phpstorm配置xdebug运行一会就停了配置虚拟机 0localhost_90.conf 配置php.ini配置下载 在下面地址下载合适的xdebug 放到对应的php https://xdebug.org/wizard 配置phpst…

python的pywebview库结合Flask和waitress开发桌面应用程序简介

pywebview的用途与特点 用途 pywebview是一个轻量级Python库&#xff0c;用于创建桌面应用程序&#xff08;GUI&#xff09;。它通过嵌入Web浏览器组件&#xff08;如Windows的Edge/IE、macOS的WebKit、Linux的GTK WebKit&#xff09;&#xff0c;允许开发者使用HTML/CSS/Java…

C#通过HslCommunication连接西门子PLC1200,并防止数据跳动的通用方法

textEdit30.Text ReadValue<int>(() > plc.ReadInt32("DB57.DBD16"), ref _last_num).ToString();// 通用读取方法&#xff08;支持所有值类型&#xff09;private T ReadValue<T>(Func<OperateResult<T>> readFunc, ref T lastValue) w…

Linux切换到Jenkins用户解决Jenkins Host key verification failed

以root或sudo user身份, 切换到jenkins用户 su -s /bin/bash jenkins前往jenkins的home目录 cd /var/lib/jenkins/查看.ssh下是否已经有known_hosts, 有的话, 是什么内容, 正常情况下, 这时候是没有对应IP记录的 cd .ssh/ more known_hosts访问一下对应IP, 记录公钥 ssh 192.16…

7.17 Java基础 | 集合框架(下)

接上文&#xff1a; 7.16 Java基础 | 集合框架&#xff08;上&#xff09;-CSDN博客 【1】Map集合 Map 集合是一种能存储键值对的数据结构。它的主要功能是依据键&#xff08;Key&#xff09;来快速查找对应的值&#xff08;Value&#xff09; 1、声明 Map<Integer,Integer…

【LeetCode刷题指南】--反转链表,链表的中间结点,合并两个有序链表

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a; 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》 ⭐️人生格言&#xff1a;生活是默默的坚持&#xff0c;毅力是永久的…

ubuntu上面的wps2019格式很乱在复制粘贴的时候

问题&#xff1a;在复制内容到 Ubuntu 上的 WPS 2019 出现如下问题&#xff1a;列表符号、换行和缩进错乱&#xff0c;表现为每行前的点符号&#xff08;•&#xff09;变成不规则对齐或空格间距不统一。原因分析✅ 主要原因是&#xff1a;WPS 2019 在 Ubuntu 上的兼容性较差&a…

bws-rs:Rust 编写的 S3 协议网关框架,支持灵活后端接入

bws-rs&#xff1a;Rust 编写的 S3 协议网关框架&#xff0c;支持灵活后端接入 bws-rs介绍 bws-rs 是一个用 Rust 编写的轻量级 S3 协议服务端网关框架&#xff0c;旨在帮助开发者快速构建兼容 AWS S3 协议 的对象存储服务。该框架支持 S3 V4 签名校验&#xff0c;集成 Axum 作…

黑马点评系列问题之p70postman报错“服务器异常”

问题描述&#xff1a;在做这个位置的时候报错报错如下控制台报错如下解决根据控制台的报错来看&#xff0c;是​Redis模板未注入导致的空指针异常经过排查&#xff0c;原因是这里少了个Resource

Docker搭建Elasticsearch和Kibana

1.安装docker&#xff0c;确保正常启动 2.按步骤操作&#xff0c;这里的es是单节点的&#xff0c;如需多节点&#xff0c;需安装docker-compose进行yml文件的编写对容器进行编排 #docker拉镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.11.2 docker pul…

【深度学习笔记 Ⅰ】3 step by step (jupyter)

1. 导包 import numpy as np import h5py import matplotlib.pyplot as plt from testCases_v2 import * from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward% matplotlib inline plt.rcParams[figure.figsize] (5.0, 4.0) # set default size of plo…

前端流式渲染流式SSR详解

以下是关于前端流式渲染及流式SSR&#xff08;Server-Side Rendering&#xff09;的详细解析&#xff0c;结合核心原理、技术实现、优化策略及实际应用场景展开说明&#xff1a;⚙️ 一、流式渲染基础原理 核心概念 ◦ 流式渲染&#xff1a;数据通过分块传输&#xff08;Chunke…

Redis通用常见命令(含面试题)

核心命令get 根据key取valueset 把key和vlaue存入进去key和value本事上都是字符串&#xff0c;但在操作的时候可以不用加上引号""Redis作为键值对的结构&#xff0c;key固定就是字符串&#xff0c;value实际上会有多种类型&#xff08;字符串哈希表&#xff0c;列表&…