设计模式(1)--java实现版
设计模式的重要性,想必大家都不言而喻。
既然我们大家要学习设计模式就需要知道设计模式的概念。
我们在编写软件的时候面临着,耦合性,内聚性和可维护、可扩展、重用性、灵活性等多方面的挑战。学习设计模式是为了让程序具有更好的代码重用性,可读性,可扩展性,可靠性,使程序呈现高内聚,低耦合。简单来说设计模式可以让你的代码更加健壮,让程序变的东西和不变的东西分离开。并且设计模式是很多前辈多年写代码经验的积累。
提到设计模式就必须说七大原则。
1)单一职责原则
2)接口隔离原则
3)依赖倒转原则
4)里氏替换原则
5)开闭原则
6)迪米特法则
7)合成复用原则
接下来我会为大家带来七大原则的详细介绍。并且用代码表达。
单一职责原则
对于类或者方法来说,即一个类应该只负责一项职责,如果让A类负责两个不同的职责,那么其中一个职责需求变更的时候可能会引起另一个职责的错误。所以应该分解为两个类。各自负责不同的职责。
使用交通工具进行案例讲解。
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("摩托车");
vehicle.run("飞机");
}
}
/***
* 交通工具类
* 方式一
* 1.在run方法中违反了单一职责原则。run方法及管陆地上又管天空上
* 2.解决方法将不同的交通工具分解为不同的类每个类有自己的run方法
*/
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上运行 .....");
}
}
上面的代码是违反单一职责原则的代码。Vehicle类既负责陆地上交通工具的运行还负责天空中交通工具的运行。
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle road = new RoadVehicle();
road.run("汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
WaterVehicle water = new WaterVehicle();
water.run("轮船");
}
}
/***
* 第二种实现方式
* 1.遵守了单一职责
* 2.缺点 改动很大,将类分解,同时需要修改客户端代码
*/
class RoadVehicle{
public void run(String vehicle) {
System.out.println(vehicle + "在公路上运行.....");
}
}
class AirVehicle{
public void run(String vehicle) {
System.out.println(vehicle + "在天空中运行.....");
}
}
class WaterVehicle{
public void run(String vehicle) {
System.out.println(vehicle + "在水中运行.....");
}
}
这是遵守单一职责原则的解决方案,缺点就是每一种类型的交通工具都有一个类,类的数量比较多。
public class SingleResponsibility3 {
public static void main(String[] args) {
Vehicle2 vehicle = new Vehicle2();
vehicle.airRun("飞机");
vehicle.roadRun("汽车");
vehicle.waterRun("轮船");
}
}
/***
* 第三种实现方式
* 1.违背了类的单一职责原则但是遵守了方法的单一职责原则
* 2.修改内容较少
* 2.对于类内容较少的时候可以使用
*/
class Vehicle2{
public void waterRun(String vehicle) {
System.out.println(vehicle + "在水中运行.....");
}
public void roadRun(String vehicle) {
System.out.println(vehicle + "在公路上运行.....");
}
public void airRun(String vehicle) {
System.out.println(vehicle + "在天空上运行.....");
}
}
这是第三种解决方案。我们还是在一个类里面实现不同交通工具的运行方法,但是每种交通工具有各自的运行方法。
我们这样做虽然违反了类的单一职责原则,但是遵守了方法的单一职责原则。
可能大家看到这里会有一点小疑惑,单一职责到底是作用在类上面还是方法上面。我认为即可以作用在类上面也可以作用在方法上面。具体看需求的复杂度,如果需求简单可以作用在方法上面,需求复杂最好还是作用在类上面。切勿生搬硬套,应该灵活运用。
下面就说说单一职责的优点:
1.降低了类的复杂度,一个类只负责一项职责。
2.提高类的可读性,可维护性。
3.降低变更引起的风险。
接口隔离原则
客户端不应该依赖它不需要依赖的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
下面我们看一张UML类图
这张图的意思是类A需要通过Interface1接口来依赖类B,类C通过接口Interface1接口来依赖D,那么接口Interface1对于类A和类C来说就不是最小接口。
(就是A的方法的形参声明的类型是Interface1,实际传入的对象是B类的对象,但是类A只需要用到operation1、operration2、operation3方法,但是类B需要实现所有的方法。)
代码实现如下
interface Interface{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface{
public void operation1() {
System.out.println("B类实现了operation1方法");
}
public void operation2() {
System.out.println("B类实现了operation2方法");
}
public void operation3() {
System.out.println("B类实现了operation3方法");
}
public void operation4() {
System.out.println("B类实现了operation4方法");
}
public void operation5() {
System.out.println("B类实现了operation5方法");
}
}
class D implements Interface{
public void operation1() {
System.out.println("D类实现了operation1方法");
}
public void operation2() {
System.out.println("D类实现了operation2方法");
}
public void operation3() {
System.out.println("D类实现了operation3方法");
}
public void operation4() {
System.out.println("D类实现了operation4方法");
}
public void operation5() {
System.out.println("D类实现了operation5方法");
}
}
class A{
public void depend1(Interface i) {
i.operation1();
}
public void depend2(Interface i) {
i.operation2();
}
public void depend3(Interface i) {
i.operation3();
}
}
class C{
public void depend1(Interface i) {
i.operation1();
}
public void depend4(Interface i) {
i.operation4();
}
public void depend5(Interface i) {
i.operation5();
}
}
这是没有遵守接口隔离原则的代码。
上面的这种请求需要遵守接口隔离原则,我们可以将接口拆分成三个。因为A类和C类都需要operation1方法所以有一个接口只有operation1方法。再有两个接口分别声明operation2,operation3方法,和operation4、operation5方法。这样就遵守了接口隔离原则。
interface Interface1{
void operation1();
}
interface Interface2{
void operation2();
void operation3();
}
interface Interface3{
void operation4();
void operation5();
}
class B1 implements Interface1,Interface2{
public void operation2() {
System.out.println("B类实现了operation2");
}
public void operation3() {
System.out.println("B类实现了operation3");
}
public void operation1() {
System.out.println("B类实现了operation1");
}
}
class D1 implements Interface1,Interface3{
public void operation4() {
System.out.println("D类实现了operation4");
}
public void operation5() {
System.out.println("D类实现了operation5");
}
public void operation1() {
System.out.println("D类实现了operation1");
}
}
class A1{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface2 i){
i.operation2();
}
public void depend3(Interface2 i){
i.operation3();
}
}
class C1{
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface3 i){
i.operation4();
}
public void depend5(Interface3 i){
i.operation5();
}
}
这样我们就遵守了接口隔离原则。
依赖倒转原则
1.抽象不应该依赖细节,细节应该依赖抽象。
2.依赖倒转原则的中心思想是面向接口编程。
3.相对于细节的多变性,抽象的东西更稳定,以抽象为基础搭建的架构比以细节为基础搭建的架构稳定的多。在java中抽象指的是接口和抽象类,细节就是实现类。
下面是没有遵守依赖倒转原则的代码
public class DependacyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public void getInfo() {
System.out.println("电子邮件信息:hello world!");
}
}
class Person{
public void receive(Email email) {
email.getInfo();
}
}
Person类有一个receive方法,依赖的是Email类。接收消息的方式有很多种,不光有Email还有微信,打电话等等。如果我们再添加一种通讯方式那么Person类就需要再重写一边receive方法,十分麻烦。
public class DependacyInversion {
public static void main(String[] args) {
new Person().receive(new Email());
new Person().receive(new WeiXin());
}
}
interface IReceive{
public void getInfo();
}
class Email implements IReceive{
public void getInfo() {
System.out.println("Email消息:hello world!");
}
}
class WeiXin implements IReceive{
public void getInfo() {
System.out.println("微信消息:hello weixin!");
}
}
class Person{
public void receive(IReceive receive) {
receive.getInfo();
}
}
这是遵守依赖倒转原则的写法。
我们将所有的通讯方式抽取出一个IReceive接口,让每种通讯方式实现接口重写getInfo方法,并且Person类的receive方法依赖接口,这样我们可以随意的添加通讯方式,而不用更改客户端代码。
依赖倒转原则的的注意事项和细节
1.底层模块应该都有接口或抽象类,二者都有更好。
2.变量的声明尽量是抽象类或接口。有利于程序的扩展,优化。
3.继承应该遵守里氏替换原则(将会在下一篇博客进行讲解)。
本篇博客介绍了设计模式中七大原则其中的三个原则下一篇博客将会继续讲解其他的四个原则。