设计模式之基本模式


1、接口

Q:什么是接口模式?
A:在提供各种各样服务的对象中,总会存在一些提供相同(或者相似)的服务,将这些共同的服务抽象为一个接口,具体实现可以由实现该接口的类去自定义。如下例子:

在设计一个计算公司员工工资的系统的时候,有如下因素需要我们去考虑:(1)员工有多个类别(2)不同类别可能不一样的计酬算
法 (3)员工的类别可能增多但计酬算法也一样

糟糕的设计是,每个类别就创建一个类,然后在类中写具体的实现方法,从个人经验看,这确实可以实现目标,但代码的维护性,可阅读性都会大大降低,另外,提供相同的服务的功能意味着代码实现是类似的,这种为实现类似功能而不断创建类似的类不仅使得整个系统的代码冗余而又笨重,还大大降低了开发的效率,而好的设计师会在充分考虑以上因素后,采用一些较为优良的方式进行代码的设计:接口就是其中一种方法。即:

//  不同对象提供的公共的薪水计算服务可以被抽象出来,成为一个独立的SalaryCalculator接口。

public interface SalaryCalculator{
     double getSalary() ;
}

其中,CategoryA和CategoryB为具体的计算方法:
在这里插入图片描述
Q:接口模式的作用?
A:(1)可用来设计那些提供相同服务的不同类,以使客户对象能够使用属于不同类的服务提供者对象,而只需对其代码需进行很少的修改,或者根本不需要修改。(2)使用接口模式,不同对象的共同的服务就可以抽象为接口。提供该服务的类设计为这个共同的接口的实现者。(3)客户对象就可以安全地假定服务提供者对象属于特定的接口类型。在类层次关系中,属于不同的服务提供者类的对象可以被当做是属于同一个接口类型的对象来对待(实现同一接口的不同实现类提供不同的服务,但它们实现的是同一接口)。

Q:接口模式所依赖的原则?
A:接口模式是依赖倒置原则的应用,高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。


2、抽象父类

Q:什么是抽象父类?

A:对于面向对象编程来说,抽象是它的一大特征。在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类。而抽象类是为成为超类(父类)而生的,它不能被直接实例化,且其子类必须实现它所有的抽象方法(如果不实现,那么子类也必须申明为抽象类)。抽象类是用abstract修饰的类,在该类中可以不含抽象方法(即含有抽象方法的类一定是抽象类,而抽象类不一定都含有抽象方法,如:HttpServlet类)

Q:抽象父类的作用?

A:最明显的作用是规范代码的书写,使其符合Java中的多态概念。从框架设计的角度而言,它可用于设计一个能够为一组相关类的公共功能提供一致的框架。其中,抽象方法具有多于一个可能的实现,代表着类族的行为中的可变部分。抽象类中也可能包含有方法的实现,这些方法则代表着类行为中的不变部分。

Q:抽象父类运用的原则?

A:运用的是里氏代换原则,所有引用基类的地方必须能透明地使用其子类的对象(任何基类可以出现的地方,子类一定可以出现)。子类可以扩展父类的功能,但不能改变父类原有的功能。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

另外,继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。

Q:抽象父类和接口的区别

A:最明显的区别,一个用interface修饰,一个用class修饰。哈哈,深层点说,接口是抽象方法的集合,而抽象父类可以有自己的实现,虽然接口也可实现其作用,但有时候,接口会产生代码冗余而抽象父类则可以简化代码(因为拥有自己的实现)。另一方面,Java只允许单继承,当你需要多个父类的某些功能时,则需要设计为接口,因为Java允许实现多个接口。

这篇文章描述了二者间的详细区别,下图出自于此
在这里插入图片描述
Q:抽象父类用到的地方,普通父类也可以完成,为啥还需要抽象父类呢?

A:很多人都知道抽象父类不能直接实例化,而普通父类可以,但作用呢?为啥这样弄呢?其实也没什么牛逼的原因,只是为了起到设计规范的作用并满足Java多态的概念。另外,实际项目开发中,会有很多不确定的功能,后续还需要很多维护,采用抽象父类容易进行维护,对子类未进行实现的功能,编译器还会报错,大大释放了我们的脑内存。


3、私有方法

用过Java开发的同学肯定知道,其实就是private修饰的方法,但很多初学者或者初次进行实际开发的道友都有一个不好的书写习惯,即方法的访问限制较为随意,只要项目正常运行,怎么限制并不关心!这其实是个不良的习惯,在《阿里巴巴Java开发手册》一文中的第21条就作出了明确的规定。

Q:那么什么时候用到private来修饰方法呢?

A:(1)在一个良好设计的类中,每个方法仅完成单一任务。其中有的方法可能会调用其他方法甚至其他对象的方法。但不是所有的方法都是打算提供给外部客户对象使用。(2)面向客户对象提供的方法集组成了一个对象的公有协议,并被声明为公有方法。而其他一些方法则可能只被别的方法或同一个对象中的内部类在内部使用。此时,私有方法模式建议将上述方法设计为私有方法。
在这里插入图片描述


4、存取器方法

存取器方法也是我们经常用到的模式,只不过我们一直不知道所写得代码有这么一个名称。它其实就是bean类中的getXXX()和setXX()方法。

Q:存取器方法的具体含义?

A:(1)所有的实例变量都声明为私有的,并提供公有的存取器方法来访问对象的公有状态。这将禁止客户对象直接访问实例变量。(2)此外,存取器方法还对客户隐藏细节:一个属性是在父类/子类中定义的。客户对象可以使用存取器方法将一个Customer对象从一个状态(原状态)转变到另一个状态(目标状态)。(3)通常,如果该对象无法达到目标状态,它应该通知调用者状态的转变无法完成。这一点可以通过使存取器抛出一个异常来完成。(例如:null赋给基本类型的变量)

Q:采用存取器方法的好处?

A:毫无疑问的一点是,大大提高了软件的可维护性。设想一下,当某个对象的实例变量是公有的,并在应用中的多处地方直接引用,那么有一天你突然修改了变量名,所有引用该变量的地方也要跟着修改了。而采用存取器方法,则避免了这些麻烦,我们只需要修改提供实例变量的访问方法就好。

Q:采用存取器方法和直接引用的比较?

A:这里写了几行简单的代码作一下比较,有一个Customer类,它有一个name属性,从图可以看到,如果此时修改变量,你需要修改多个地方,当然,现在的IDE强大了很多,可以直接修改多个地方,但如果你的项目很大,并且存在具有不同意义的同名变量,那你就麻烦了。
在这里插入图片描述
在这里插入图片描述


5、常数管理器

程序设计中通常都会使用不同类型的常量,常见的常量数据包括数据文件名、界面文字、取值范围的最大值和最小值、错误码以及错误信息等。而常量数据管理器模式建议,将常量统一放在一个对象中供其他对象访问,其定义不出现在其他类中,这种模式能为常量数据提供一个易于维护的集中式仓库。

如下:ConstantDateManger类中存放着多个常量,当需要修改时,只需要该类就行。
在这里插入图片描述
值得一提的是,这种方法确实方便了我们对常数的管理,但并不意味着,你的项目所有常数就应该都放在一个类中。正确的做法是,根据不同用途,不同结构层次进行划分,将常量分门别类。


6、不可变对象

这处理并发的应用场景时,我们经常需要担心的一个问题就是数据的脏读。而有些地方,我们初始化对象后并不希望它被改变,此时我们可以采用该模式来设计类。

如下:

public final class EmployeeModel {

  //State
  private final String firstName;
  private final Car car;

  //Constructor
  public EmployeeModel(String fn, Car c) {

    firstName = fn;
    car = c;
  }

  //Getters
  public String getFirstName() {
    return firstName;
  }
 
  public Car getCar() {
    //return a copy of the car object
    return (Car) car.clone();
  }
}

首先,为了发生其他类通过继承该类后,对数据进行修改,申明为final,另外,数据的设置只能在构造方法中完成,这样确保了线程的安全性,如果申明为不变对象的类中含有其他对象的实例,如上图代码的Car对象,此时我们在读取时,应该返回一个克隆的实例,之所以这样做是因为,即使你申明为final,对于引用变量而言,不变的是引用变量本身,它指向的内容还是可以变的。

Q:哪些类可以设计成不变对象?

A:不变对象一般针对的是数据对象,即作为数据的携带者,而不具有任何具体的行为(除了存取器)的类,它们的实例被称做数据对象。

注意:尽管设计成不变对象的类满足了多线程下,程序的运行要求,但实际开发中,变动的因素很多,在采取这种方式进行设计时应该深思熟虑,毕竟如果回头修改,又是一项麻烦的工作。


7、管程

Q:管程的应用场景

A:程序需要访问诸如文件之类的共享资源。在多线程环境中,多于一个线程同时访问这些资源时,可能会导致不可预期的行为,如:数据的脏读。这种并发访问而导致不正确和不规则行为的情况称为竞态条件。

Q:管程的设置方式?

A:管程通过对资源加锁,保证在任意一个时刻只有一个线程可以访问该对象的任何方法。客户对象无需负责如何保持同步,由服务提供者对象对同步访问负责。Java中加锁的形式主要有两种,一种是采用synchronized关键字,另一种是通过Java.util.concurrent包中的lock接口和ReentrantLock实现类 进行上锁。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

legendaryhaha

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值