设计模式(一)
-
七大原则
-
单一职责原则
方法级别的单一职责原则:就是一个类中,有多个方法去针对不同的对象给出不同的具体实现,也就是一个方法只负责一个具体的功能
类级别的单一职责原则
一个类只负责某个功能的模块的实现,并且这个列中的方法也是只针对于这个类对象的,这个在web开发中经常用到的就是MVC三层架构中每一层都分为各个模块,然后再不同的类中给出对应模块的实现方法。
单一指责原则简单案例(交通工具类的设计)
结构图:
接口隔离原则
理解:
当一个类去实现某一个接口的时候需要实现接口中的所有方法,这个时候如果实现类只需要使用到的是借口中的部分方法时,其余的方法就很多多余了。所以就需要将接口分成更小的部分,每个接口中只有部分方法,这样一来,实现类就可以通过实现单个或多个接口来实现之前接口中的部分方法,而没有必要去实现接口中所有的方法。
uml类图说明:
不好的实现方式:
根据接口隔离原则改进
改进之后的不同之处在于原先接口中的各个方法都是放在一个接口中,通过将原来的接口拆分成几个小的接口,这样一来,A依赖于B中的各个1,2,3方法就只需要通过实现两个就可以实现了。同理对于C依赖于D中的1,4,5方法的话也只需要实现两个 不同的接口就可以直接实现了
- 依赖倒转原则
理解:
细节依赖抽象,高层模块不应依赖于低层模块,并且这二者都应该去依赖于接口,也就是说在传递类作为参数的时候应该尽可能的传递某个类的接口或抽象类,而不是传递一个具体的实现类,即使这个类是比较高层的并且不常常修改的,毕竟谁也不知道这个类什么时候要改。
上面的这些其实核心都是面向接口编程。
举例:设计一个电视机类,并且实现开关电视的功能
代码实现1(接口实现依赖倒转)
package demo.principle.dependencyInverse;
public class DependencyInverse1 {
public static void main(String[] args) {
IOpenAndClose io = new HaierOpenClose();
ITv tv = new Haier();
io.open(tv);
io.close(tv);
}
}
// 方式1, 接口依赖倒转
// 电视机有多个种类,而打开关闭电视机的方式也是有多个种类,所以对于打开关闭需要依赖的是一个电视机接口,同时打开关闭需要
// 针对不同的电视机实现不同的功能,这个地方用到的就是单一指责原则,对于这个打开和关闭的接口,可以使用方法级别的单一职责。
interface ITv{
void paly();
void close();
}
// 执行电视机操作的接口
interface IOpenAndClose{
void open(ITv tv); // 通过依赖电视机接口去依赖具体的电视机,然后调用电视机功能
void close(ITv tv);
}
// 具体的电视机实现类
class Haier implements ITv{
@Override
public void paly() {
System.out.println("海尔电视机开始运行");
}
@Override
public void close() {
System.out.println("海尔电视机关闭");
}
}
// 开关操作的实现类
class HaierOpenClose implements IOpenAndClose{
@Override
public void open(ITv tv) {
tv.paly();
}
@Override
public void close(ITv tv) {
tv.close();
}
}
UML类图:
实现方式2:(构造方法实现依赖倒转)
代码实现:
package demo.principle.dependencyInverse;
public class DependencyInverse2 {
public static void main(String[] args) {
// 因为在同一个包下,相同的类和接口会存在冲突,重新这几个类,不过对于实际应用中只需要一个接口即可,几种
// 依赖倒转方式也只需要一种实现就行。
IOpenAndClose2 io = new HaierOpenClose2();
ITv2 tv = new Haier2();
io.open();
io.close();
}
}
// 方式2:构造器实现依赖倒转,和接口依赖倒转不同 的是这里不直接在IOpenAndClose2接口中传递ITv2接口,而是在其实现类中
// 增加一个成员变量为ITv
interface ITv2{
void paly();
void close();
}
// 执行电视机操作的接口
interface IOpenAndClose2{
void open(); // 通过依赖电视机接口去依赖具体的电视机,然后调用电视机功能
void close();
}
// 具体的电视机实现类
class Haier2 implements ITv2{
@Override
public void paly() {
System.out.println("海尔电视机开始运行");
}
@Override
public void close() {
System.out.println("海尔电视机关闭");
}
}
// 开关操作的实现类
class HaierOpenClose2 implements IOpenAndClose2{
private ITv2 tv;
// 构造器将tv属性实例化,从而实现对象注入到HaierOpenClose2类中
public HaierOpenClose2() {
this.tv = new Haier2();
}
@Override
public void open() {
this.tv.paly();
}
@Override
public void close() {
this.tv.close();
}
}
UML类图
实现方式3:(set方法实现依赖倒转)
代码实现:
package demo.principle.dependencyInverse;
public class DependencyInverse3 {
public static void main(String[] args) {
// 因为在同一个包下,相同的类和接口会存在冲突,重新这几个类,不过对于实际应用中只需要一个接口即可,几种
// 依赖倒转方式也只需要一种实现就行。
IOpenAndClose3 io = new HaierOpenClose3();
ITv3 tv = new Haier3();
io.setTv(tv);
io.open();
io.close();
}
}
// 方式2:set方法实现依赖倒转,这个只需要将构造器方式改一下就行
interface ITv3{
void paly();
void close();
}
// 执行电视机操作的接口
interface IOpenAndClose3{
void open(); // 通过依赖电视机接口去依赖具体的电视机,然后调用电视机功能
void close();
void setTv(ITv3 tv);
}
// 具体的电视机实现类
class Haier3 implements ITv3{
@Override
public void paly() {
System.out.println("海尔电视机开始运行");
}
@Override
public void close() {
System.out.println("海尔电视机关闭");
}
}
// 开关操作的实现类
class HaierOpenClose3 implements IOpenAndClose3{
private ITv3 tv;
@Override
public void setTv(ITv3 tv) {
this.tv = tv;
}
@Override
public void open() {
this.tv.paly();
}
@Override
public void close() {
this.tv.close();
}
}
UML类图:
依赖倒转原则细节:
底层(具体实现那层)尽量要有接口或抽象类,或者两者都有。对象之间的依赖尽可能的通过接口进行依赖而不是直接通过具体实现类进行依赖,这样一来在对象和对象之间就存在一个缓冲层,有利于扩展。
- 里氏替换原则(达到所有引用基类的地方都是透明的,也就是说在所有具体实现类使用的时候都能明确的知道他使用的是自己的方法还是父类的方法)
举例分析:
代码:
**不好的方式:**
package demo.principle.lissubstitution;
public class LsSubstitution {
public static void main(String[] args) {
A a = new A();
System.out.println("a+b = "+a.fun1(4, 5));
System.out.println("-----------------------");
B b = new B();
// 之所以会这么写是因为假设了程序员没有意识到自己重写了父类的方法,所以认为调用的还是父类的方法
System.out.println("a+b = "+b.fun1(6, 7));
}
}
class A{
// 求和
public int fun1(int a, int b) {
return a+b;
}
}
class B extends A{
// 求差,但是实际上程序员在写这个方法实现上是可能是不会想到自己已经重写了父类的方法(其实好像这个说法就不大对,既然
// 继承了,又怎么会不知道自己是不是重写了父类的方法呢,但是理解上好像是这么个意思)
public int fun1(int a, int b){
return a-b;
}
}
**改进的方式:**
package demo.principle.lissubstitution;
public class LsSubstitution2 {
public static void main(String[] args) {
A2 a = new A2();
System.out.println("a+b = "+a.fun1(4, 5));
System.out.println("-----------------------");
B2 b = new B2();
b.setA(a);
// 通过去掉B类和 A类的继承关系,这样在使用B类的时候就不会再因为A类是其父类而直接调用父类方法了。
// 也就很明确了B类使用时候的功能了
System.out.println("a-b = "+b.fun1(6, 7));
System.out.println("a+b = "+b.getA().fun1(6, 7));
}
}
// 构建一个更顶层的base类,从而去掉A类和B类之间的继承关系
class Base{
}
class A2 extends Base{
// 求和
public int fun1(int a, int b) {
return a+b;
}
}
class B2 extends Base{
// 求差,但是实际上程序员在写这个方法实现上是可能是不会想到自己已经重写了父类的方法(其实好像这个说法就不大对,既然
// 继承了,又怎么会不知道自己是不是重写了父类的方法呢,但是理解上好像是这么个意思)
private A2 a;
// 将A和B类聚合
public void setA(A2 a) {
this.a = a;
}
public A2 getA() {
return a;
}
public int fun1(int a, int b){
return a-b;
}
}
PS: 其实对这个里氏替换原则还是不大理解,怎么可能会出现继承一个类而不知道自己啥时候重写了父类方法的情况。。
5. 开闭原则:
其实这个就是通过接口来使用其子类功能,前边的依赖倒转其实也是使用了开闭原则,还有这学期上课说到的接口使用子类实现求矩形面积也是使用了开闭原则。实际开发的时候再补充吧,感觉真的没什么好写的。
6. 迪米特法则
就是类和类之间的耦合较低,只有直接朋友,尽可能的避免使用间接朋友。
什么是直接朋友呢?一个类的参数,返回值,中使到了其他的类,这些类叫做直接朋友。反之,在方法中突然出现的(new出来的)则是间接朋友。为什么要遵循这个迪米特法则呢,这是为了避免在程序模块的修改是影响到其他功能模块的功能。
案例:设计类输出学校总部和学院的员工信息
代码:
package demo.principle.demite;
import java.util.ArrayList;
import java.util.List;
/**
* 有一个学校,学校里包括学校总部和各个学院,要求设计输出学校总部的员工id和学院的员工id
* @author My
*
*/
public class Dimite {
public static void main(String[] args) {
System.out.println("输出学校员工信息如下");
ScoolHQ scoolHQ = new ScoolHQ();
new ScoolHQManager().printHQEmploee(scoolHQ);
System.out.println("================================");
Colleage colleage = new Colleage();
new ColleageManager().printEmployee(colleage);
}
}
//员工
class Employee{
private String id;
public Employee(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
// 学校总部
class ScoolHQ{
// 直接朋友,Employee,间接朋友,无
private List<Employee> list = new ArrayList<>();
public ScoolHQ() {
for (int i = 0; i < 10; i++) {
list.add(new Employee(String.valueOf(i)));
}
}
public List<Employee> getEmploee() {
return this.list;
}
}
//
class ScoolHQManager{
// 直接朋友ScoolHQ,间接朋友,无
public void printHQEmploee(ScoolHQ hq) {
List<Employee> list = hq.getEmploee();
for (int i = 0; i < list.size(); i++) {
System.out.println("第"+(i+1)+"个员工的id为"+list.get(i).getId());
}
}
}
// 学院
class Colleage{
// 直接朋友 Employee,简介朋友,无
List<Employee> list = new ArrayList<>();
public Colleage() {
for (int i = 0; i < 5; i++) {
list.add(new Employee("0"+String.valueOf(i)));
}
}
public List<Employee> getColleageEmployee() {
return this.list;
}
}
class ColleageManager{
// 直接朋友Colleage, 简介朋友,无
public void printEmployee(Colleage colleage){
List<Employee> list = colleage.getColleageEmployee();
int count = 1;
for (Employee employee : list) {
System.out.println("第"+(count++)+"员工的id为"+employee.getId());
}
}
}
7.合成复用原则
尽量避免只用继承关系,而使用聚合,组合这些关系去代替。
8. uml类图关系
uml类图基本符号:
8.1 依赖关系
一个类A中用到了其他类B, 类C,也就是一个类B,C对象作为类A的成员变量。 什么是使用到呢?包括这些情况,在类A中的方法参数中用到,返回值中用到, 成员属性,方法内部用到。
类图:
8.2 泛化(实际上就是继承关系, 依赖关系的特例)
如果A继承了B类,那么AB之间就是继承关系(很简单的实现,不画类图了)
8.2 实现(类实现接口, 也是依赖关系的特例),类图略
8.3 关联关系(依赖关系的特例),类图略,懂得下边的聚合和组合之后这个应该就明白了,和数据库中的关联是差不多的,只是这变成了类之间的关联
类与列之间的一对一,一对多,多对多的关系
8.4 聚合关系(关联关系的特例)
具有导航型和多重性
整体和部分可以分开
注意,在聚合关系中,主类的中包含的其他类作为成员对象,但是没有在主类创建(即new 一个对象)的时候立即将其成员实例化,这意味着主类创建的时候对于其他类的成员不是必须的(很显然,成员没有实例化为空嘛),这一点是区别于组合关系的。在类图中的体现是主类的成员字段并没有直接跟上一个new(实例化的操作),可以对比着组合关系的类图查看。
8.5 组合关系
整体和部分不可分开
um类图
在Computer主类构造的时候KeyBoard和MOnitor就被实例化了,也就是说一个电脑在创建的时候就必须要有一个键盘和一个显示屏。
在某些情况下组合关系可能会变成聚合关系,聚合关系也可能变成组合关系,比如说在设计一个人person,和其对应的IDcard在删除的时候就是组合关系,删除一个人的时候需要将其对应的身份证号删除, 而在开始定义的时候人和身份证是聚合关系,一个人不一定要有身份证。
附录:
eclipseUML类图设计环境搭建:
1. 下载GEF:
eclipse->help->install new SoftWare
将`GEF - http://download.eclipse.org/tools/gef/updates/releases/`拷贝到图片中所示位置,或者点击add,分别将名称和连接填入对应的两个输入框。链接前半部分是名称,后半部分是GEF下载地址。
- 安装amateras插件
从[http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=AmaterasUML](http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=AmaterasUML)
下载压缩包,解压后将三个jar包拷贝到eclipse的plugins文件夹下,重启eclipse,然后新建文件时输入class,就会看得到class Diagram, 选择class Diagram就是类图了(其他amateras是其他种类的图,感兴趣自行了解,设计模式主要用的是类图)。
今日内容总结:
基本粗枝大叶的学了边设计模式的七大原则,还有UML类图的基本使用,不过这些案例并不是自己想出来的。一时也想不到什么比较好的例子。好友就是,案例代码时根据自己的理解写的,不敢保证理解的正确性,日后在做项目实战中用到了再回来改吧。