西安营销型网站制作价格,沈阳seo关键词排名优化软件,大城县企业网站建设,网络空间设计说明怎么写面向对象 文章目录面向对象04 类封装接口 抽象类05 消息#xff0c;实例化#xff0c;静态变量方法消息动/静态类型语言对象创建类及实例具有下面特征对象数组的创建静态数据成员构造函数06_0 继承继承是向下传递的JAVA为什么不支持多重继承继承的形式特殊化继承替换原则规范…面向对象 文章目录面向对象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,i1);静态数据成员
利用static关键字修饰的数据成员称为静态数据成员/静态数据字段/静态变量/类成员也称为静态成员全局成员)。
利用static关键字修饰的成员方法为类方法/静态成员方法类方法可以由类直接调用
静态成员不需要实例化就存在而非静态成员是实例化后才有的成员在没有实例化之前非静态成员并不存在。因此可以利用仅仅在某一时刻存在的对象访问普遍存在的对象而不能用一个普遍存在的对象访问仅仅在某一时刻存在的对象。
对象本身不对共享字段初始化。内存管理器自动将共享数据初始化为某特定值每个实例去测试该特定值。第一个进行测试的做初始化。
Java中静态数据字段的初始化是在加载类时执行静态块来完成。
静态数据字段静态变量与静态方法都是在类从磁盘加载至内存后被创建的与类同时存在同时消亡。
构造函数
缺省构造方法没有参数的构造方法。如果程序员不提供任何来构造方法则编译程序自动提供一个构造方法只要程序员提供了一个构造方法系统不再提供缺省构造方法
编译程序提供的缺省构造方法只做一件事调用父类的缺省构造方法
06_0 继承
继承是向下传递的
子类所具有的数据和行为总是作为与其相关的父类的属性的扩展( extension)即更大的集合。
子类具有父类的所有属性以及其他属性。
继承总是向下传递的因此一个类可以从它上面的多个超类中继承各种属性 。
派生类可以覆盖从基类继承来的行为。
JAVA为什么不支持多重继承
因为当一个类同时继承两个父类时两个父类中有相同的功能则子类对象调用该功能时无法确定运行的是哪一个
但是java支持多级继承。A继承B B继承C C继承D。
多级继承的出现就有了继承体系。
体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。
继承的形式
特殊化继承
很多情况下都是为了特殊化才使用继承。
在这种形式下新类是基类的一种特定类型它能满足基类的所有规范。
用这种方式创建的总是子类型并明显符合可替换性原则。
替换原则
子类可以扩展父类的功能但不能改变父类原有的功能
子类可以实现父类的抽象方法但不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重写父类的方法时方法的前置条件即方法的形参要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时方法的后置条件即方法的返回值要比父类更严格。
规范化继承
基类中既有已实现的方法也有只定义了方法接口、留待派生类去实现的方法。
派生类只是实现了那些定义在基类却又没有实现的方法。
派生类并没有重新定义已有的类型而是去实现一个未完成的抽象规范。
构造继承
子类使用父类提供的行为但并不是父类的子类型
子类通过继承父类就可以实现其需要的行为只需要对方法名称、方法参数等进行修改
泛化继承
子类修改或改写父类的某些方法
扩展继承
子类只是往父类中添加新行为并不修改从父类继承来的任何属性
由于父类的功能仍然可以使用而且没有被修改因此扩展继承并不违反可替换性原则用这种方式构建的子类还是子类型
限制继承
如果子类的行为比父类的少或更严格就出现了限制继承
变体继承
子类和父类之间都是对方的变体可以任意选择两个之间的父子关系
例如用为控制鼠标的代码与用来控制图形输入板的代码几乎完全相同。在概念上没有理由让一个类作为另外一个类的父类因此可以选择任何一个类作为另外一个类的父类
合并继承
可以通过合并两个或者更多的抽象特性来形成新的抽象。
一个类可以继承自多个基类的能力被称为多重继承 。
06_1 多态动态静态替换
静态类动态类
变量的静态类是指用于声明变量的类。静态类在编译时就确定下来并且再也不会改变。类型由声明该变量时使用的类型决定。
变量的动态类指与变量所表示的当前数值相关的类。动态类在程序的执行过程中当对变量赋新值时可以改变。类型由实际赋给该变量的对象决定。
如果变量的动态类和静态类的类型不一致会出现所谓的多态。
多态的四种形式重载改写多态变量泛型
重写
在继承情况下子类中定义了与其基类中方法具有相同名称、相同类型签名相同返回值类型或兼容类型和相同参数类型的方法但重新编写了方法体
两种不同的关于改写/覆盖/重写的解释方式
代替replacement在程序执行时实现代替的方法完全覆盖父类的方法。即当操作子类实例时父类的代码完全不会执行改进(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 语言如果两个或者更多的方法具有相同的名称和相同的参数数目则编译器将使用下面的算法来确定如何匹配
找到所有可能进行调用的方法亦即各个参数实参可以合法地赋值给各个参数类型形参的所有方法。如果找到一个在调用时可以完全匹配所使用的参数类型的方法那么就执行这个方法。对于第一步所产生的集合中的方法两两进行比较如果一个方法的所有参数类型都可以赋值给另外一个方法那么就将第二个方法从集合中移走。重复以上操作直至无法实现进一步的缩减为止。如果只剩下一个方法那么这个方法就非常明确了调用这个方法即可。如果剩余的方法不止一个那么调用就产生歧义了此时编译器将报告错误。
重定义
当子类定义了一个与父类具有相同名称但类型签名不同的方法时发生重定义。
类型签名的变化是重定义区别于改写/覆盖/重写的主要依据。
重写和重载的区别
方法的改写/覆盖是子类和父类之间的关系而重载一般是同一类内部多个方法间的关系
方法的改写/覆盖一般是两个方法间的而重载时可能有多个重载方法
改写/覆盖的方法有相同的方法名和形参表而重载的方法只能有相同的方法名不能有相同的形参表
改写/覆盖时区分方法的是根据被调用方法的对象而重载是根据参数来决定调用的是哪个方法
用final修饰的方法是不能被子类覆盖的只能被重载
重写方法不能使用比被重写的方法更严格的访问权限重载可以有不同的访问修饰符
重写子类方法不能抛出比父类方法更多的异常。子类方法抛出的异常必须和父类方法抛出的异常相同或者子类方法抛出的异常类是父类抛出的异常类的子类重载可以抛出不同的异常
分配方案
最小静态空间分配只分配基类所需的存储空间。
最大静态空间分配无论基类还是派生类都分配可用于所有合法数值的最大的存储空间。
动态内存分配只分配用于保存一个指针所需的存储空间。在运行时分配对象所需的存储空间同时将指针设为相应的合适值地址。
06_2 多态
多态变量
多态变量是指可以引用多种对象类型的变量。
这种变量在程序执行过程可以包含不同类型的数值。
对于动态类型语言所有的变量都可能是多态的。对于静态类型语言多态变量则是替换原则的具体表现。
多态的运行机制
Java多态机制是基于“方法绑定binding”就是建立method call方法调用和method body方法本体的关联。如果绑定动作发生于程序执行前由编译器和连接器完成称为“先期绑定”。对于面向过程的语言它们没有其他选择一定是先期绑定。比如C编译器只有一种method call就是先期绑定。C有先期联编和后期联编
当有多态的情况时解决方案便是所谓的后期绑定late binding绑定动作将在执行期才根据对象型别而进行。后期绑定也被称为执行期绑定run-time binding或动态绑定dynamic binding。
Java的所有方法只有finalstaticprivate和构造方法是前期绑定其他都使用后期绑定。 将方法声明为final型可以有效防止他人覆写该函数。但是或许更重要的是这么做可以“关闭”动态绑定。或者说这么做便是告诉编译器动态绑定是不需要的。于是编译器可以产生效率较佳的程序代码。
方法的动态绑定过程 编译器检查对象的声明类型和方法名。假设我们调用x.f(args)方法并且x已经被声明为C类的变量那么编译器会列举出C类中所有的名称为f的方法和从C类的超类继承过来的f方法 接下来编译器检查方法调用中提供的参数类型。如果在所有名称为f 的方法中有一个参数类型和调用提供的参数类型最为匹配那么就确定调用这个方法 重载解析) 当程序运行并且使用动态绑定调用方法时虚拟机必须调用同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 A
② A and A
③ A and D
④ B and A
⑤ B and A
⑥ A and D
⑦ B and B
⑧ B and B
⑨ A 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()方法
消除二义性的方法
使用相应的类名来标识
device.Device1::showPower();
在派生类中重定义有名称冲突的成员
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 ss); }
} /** * 工厂类用来创造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 ArrayListObserver 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 subjectnew ConcreteSubject();
Observer observernew 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的时候执行的功能。
目标对象和观察者对象的解耦又保证了无论观察者发生怎样的变化目标对象总是能够正确地联动过来。