最近工作不是很忙,闲下来的时间来学习下23种设计模式。工作中有时会感觉项目有新功能需要开发时,代码不好改;有时看一份新的项目代码会感觉难以阅读、不知所云,这些情况我认为可能就是开发人员写代码的时候运用的设计模式不好或者就没有注意到需要使用设计模式来提高项目的可读性和可维护性,看难以读懂不好修改的代码我想我们都会很难过。
说到可维护性,作为一个android开发,在开发初期应用一个好的设计框架也很重要,比如使用mvp、mvvm写出的项目就比所有的逻辑页面都写在activity要好维护易读,这个在功能繁多逻辑复杂的页面体现的尤为突出。还有就是写项目代码并不是写的越短越好,写项目不是算法比赛,而是需要我们写的语义明确,当然精简而易读的代码最好。ok,言归正传,别的先不说了。
依赖倒置原则 (Dependence Inversion Principle-DIP)
两层含义:
1. 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象;
2. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象;
对于该原则,记住一点,面向接口编程。其中依赖的三种写法如下:
//1.构造函数传递依赖对象
public Driver(ICar car) {
this.car = car;
}
//2.setter方法传递依赖对象
public void setCar(ICar car) {
this.car = car;
}
//3.接口申明依赖对象
public void drive(ICar car) {
car.run();
}
为什么?
我想养一只狗。
public class Me{
public want(Dog dog){
System.out.println("I want a "+dog.getName());
}
}
class Dog{
public String getName(){
return "dog";
}
}
public static void main(String[] args){
Me me=new Me();
Dog dog=new Dog();
me.want(dog);//我想养一只狗
}
但是第二天我改变主意了,我想养一只猫。
class Cat{
public String getName(){
return "cat";
}
}
public static void main(String[] args){
Me me=new Me();
Cat cat=new Cat();
me.want(cat);//我想养一只猫,这样就会报错
}
这个时候就只有改变want方法的代码才能实现我想养只猫。
怎么做?
我们根据依赖倒置原则,可以设计一个抽象的animal类。然后让dog和cat的实体都依赖animal抽象,上面的问题就可以很好的解决。animal抽象类会比较稳定不会经常变动,而dog和cat的实体有许多细节需要变动,以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。
public class Me{
public want(Animal an){
System.out.println("I want a "+an.getName());
}
}
abstract class Animal{
public abstract String getName();
}
class Dog extends Animal{
public String getName(){
return "dog";
}
}
class Cat extends Animal{
public String getName(){
return "cat";
}
}
public static void main(String[] args){
Me me=new Me();
Dog dog=new Dog();
Cat cat=new Cat();
me.want(dog);//我想养一只狗
//第二天
me.want(cat);//我想养一只猫
}
单一职责原则(Single Responsibility Principle-SRP)
含义:所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
这个原则理解起来很简单,就是一个类尽量负责一种事物。防止一个类需要修改的时候对其他的地方产生影响。这个原则说起来简单,但是我们在项目中往往很容易忽视它,而忽视它很大程度是因为我们懒,修改随意,最后导致项目越来越难以维护。
为什么?
狗和猫的叫声不同。
public class Animal{
public void speak(String type){
if("dog".equals(type)){
System.out.println("汪汪汪");
}
else if("cat".equals(type)){
System.out.println("喵喵喵");
}
}
public static void main(String[] args){
Animal animal=new Animal();
animal.speak("dog");//狗叫
animal.speak("cat");//猫叫
}
}
上面的代码实现了狗叫和猫叫,但是违背了单一职责原则,这时如果想听听鸭子叫就很尴尬了,需要修改animal的speak方法。这要是在一个大型的项目中可能修改一个方法就没有难么容易了,我们还要考虑这个方法修改后会不会对已经使用了这个方法的地方造成影响。但是如果我们事先考虑了单一职责原则,这个speak方法就可以不做任何修改。
怎么做?
我们根据单一职责原则,需要把不同的职责(狗叫,猫叫,鸭子叫)分配给对应的类(狗,猫,鸭子)
public class Animal{
public void speak{};
class Dog extends Animal{
public void speak(){
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
public void speak(){
System.out.println("喵喵喵");
}
}
class Duck extends Animal{
public void speak(){
System.out.println("嘎嘎嘎");
}
}
public static void main(String[] args){
Dog dog=new Dog();
Cat cat=new Cat();
Duck duck=new Duck();
dog.speak();//狗叫
cat.speak();//猫叫
duck.speak();//鸭子叫
}
}
这样只要有一种新的动物,只需要实现这种新的动物的叫声就可以了。
不过这也会带来一个缺点,相信大家也看到了,类的膨胀。实现单一职责会多创建更多的类,增加内存消耗,这在内存资源比较珍贵的设备上问题尤为突出,如移动端开发,需要具体情况具体分析。
开放封闭原则(open closed principle-OCP)
关于开放封闭原则,其核心的思想是:
软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
因此,开放封闭原则主要体现在两个方面:
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
主要是为了不影响已有系统的稳定性。从以下几个方面来看:
1. 抽象约束;
2. 元数据控制模块行为(用配置文件管理);
3. 制定项目章程;
4. 封装变化;
为什么?
还是刚刚的例子,狗和猫的叫声不同。这时我们想加一个功能
public class Animal{
public void speak(String type){
//对speak做修改
System.out.println("不小心写了个bug");
return;
if("dog".equals(type)){
System.out.println("汪汪汪");
}
else if("cat".equals(type)){
System.out.println("喵喵喵");
}
}
public static void main(String[] args){
Animal animal=new Animal();
animal.speak("dog");//狗叫
animal.speak("cat");//猫叫
}
}
speak被修改后,由于某些原因产生了bug。在main方法中调用狗叫,猫叫这时已经不再会有正确的功能了。也说明如果方法对修改开放,就有可能对其他调用此方法的地方产生影响,所以尽量做到不要修改speak方法,而是扩展。
怎么做?
不要修改speak,而是加一个
public class Animal{
public void speak(String type){
if("dog".equals(type)){
System.out.println("汪汪汪");
}
else if("cat".equals(type)){
System.out.println("喵喵喵");
}
}
public void duckSpeak(){
System.out.println("不小心写了个bug");
System.out.println("嘎嘎嘎");
}
public static void main(String[] args){
Animal animal=new Animal();
animal.speak("dog");//狗叫
animal.speak("cat");//猫叫
animal.duckSpeak();
}
}
这样即使新方法出现bug也不会影响到已有方法,当然类在设计的时候就应该尽量考虑到如何扩展新功能。
接口隔离原则(Interface Segregation Principle-ISP)
详细定义:客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。
需要注意的是,我们在根据接口隔离原则拆分接口时,必须首先满足单一职责原则。
另外再拆分时注意:接口尽量小,接口要高内聚,定制服务(一个模块一个服务),接口设计粒度要适度
为什么?
动物属性很多,有会走路的,有会飞的,有会叫的,有会游的
interface Animal{
void walk();
void fly();
void speak();
void swim();
}
class Dog implements Animal{
void walk(){
System.out.println("四条腿走");
}
void fly(){}//冗余代码
void speak(){
System.out.println("汪汪汪");
}
void swim(){}//冗余代码
}
class Bird implements Animal{
void walk(){
System.out.println("两条腿走");
}
void fly(){
System.out.println("翅膀飞");
}
void speak(){
System.out.println("叽叽叽");
}
void swim(){}//冗余代码
}
class Fish implements Animal{
void walk(){}//冗余代码
void fly(){}//冗余代码
void speak(){}//冗余代码
void swim(){
System.out.println("用尾巴游");
}
}
我们会发现狗,鸟,鱼都是动物,但是狗不能飞不能游,鸟不能游,鱼不能走不能飞不能叫,但是他们作为动物又不得不实现对应接口的方法,这样就造成了大量的冗余代码,继而导致项目难以维护。
怎么做?
interface CanWalkAndSpeak{
void walk();
void speak();
}
interface CanFly{
void fly();
}
interface CanSwim{
void swim();
}
class Dog implements CanWalkAndSpeak{
void walk(){
System.out.println("四条腿走");
}
void speak(){
System.out.println("汪汪汪");
}
}
class Bird implements CanWalkAndSpeak,CanFly{
void walk(){
System.out.println("两条腿走");
}
void fly(){
System.out.println("翅膀飞");
}
void speak(){
System.out.println("叽叽叽");
}
}
class Fish implements CanSwim{
void swim(){
System.out.println("用尾巴游");
}
}
迪米特原则(Law of Demeter-LOD)
又叫作最少知识原则(Least Knowledge Principle 简写LKP)
详细含义:一个类应该对自己需要耦合或调用的类知道得越少越好。
像诸如getA().getB().getC().getD()形式一定不要出现(JDK API除外)
同时一个类对外公布的接口越少越好
为什么?
还是动物的例子,动物下面包含有哺乳动物,鸟类,鱼类
class Animal{
private Mammal mammal=new Mammal();//哺乳动物
private Bird bird=new Bird();//鸟类
public void showAll(){
mammal.getDog().showAll();//所有的狗
mammal.getCat().showAll();//所有的猫
bird.getChook().showAll();//所有的鸡
bird.getDuck().showAll();//所有的鸭
}
}
class Mammal{
//哺乳动物中有狗和猫
private Dog dog=new Dog();
private Cat cat=new Cat();
public Dog getDog(){
return dog;
}
public Cat getCat(){
return cat;
}
}
class Bird{
//鸟类中有鸡和鸭
private Chook chook=new Chook();
private Duck duck=new Duck();
public Chook getChook(){
return chook;
}
public Duck getDuck(){
return duck;
}
}
class Dog{
public void showAll{
System.out.println("各种各样的狗:金毛、萨摩耶、单身狗...");
}
}
class Cat{
publc void showAll{
System.out.println("各种各样的猫:波斯猫、折耳猫...");
}
}
class Chook{
publc void showAll{
System.out.println("各种各样的鸡:乌鸡、火鸡...");
}
}
class Duck{
publc void showAll{
System.out.println("各种各样的鸭:...");
}
}
这里我们会发现Animal类和Bird类,Mammal类的耦合度提高了,我们不仅仅要知道Animal里包含鸟和哺乳动物,还要知道鸟类中包含鸡和鸭,哺乳动物中包含狗和猫,才能把所有的动物全部显示出来。这在项目中会大大增加开发人员的维护难度。
怎么做?
class Animal{
private Mammal mammal=new Mammal();//哺乳动物
private Bird bird=new Bird();//鸟类
public void showAll(){
mammal.showAll();//所有的哺乳动物
bird.showAll();//所有的鸟类
}
}
class Mammal{
//哺乳动物中有狗和猫
private Dog dog=new Dog();
private Cat cat=new Cat();
//展示所有的哺乳动物
public void showAll(){
dog.showAll();
cat.showAll();
}
}
class Bird{
//鸟类中有鸡和鸭
private Chook chook=new Chook();
private Duck duck=new Duck();
//展示所有的鸟类
public void showAll(){
chook.showAll();
duck.showAll();
}
}
如果像上面那样写,我们在需要展示所有的动物的时候,只需要关心哺乳动物和鸟如何展示其自身所有的,不必关心哺乳动物和鸟的内部的分类。
里氏替换原则(liskov substitution principle-LSP)
里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏代换原则是实现开闭原则的重要方式之一。
详细定义:只要父类能出现的地方,子类就可以出现
通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。
主要包含四个部分:
子类必须完全实现父类的方法;
子类可以有自己的个性(因此里氏替换原则可以正着用,却不可以反过来用);
覆盖或实现父类的方法时输入参数可以被放大;
覆写或实现父类的方法时输出结果可以被缩小;
为什么?
class Animal {
public void eat() {
System.out.println("吃东西");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("回到自己的狗窝里");
}
public void sleep() {
eat();
System.out.println("睡觉");
}
}
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println("吃饭时间到:");
animal.eat();
Dog dog = new Dog();
System.out.println("睡觉时间到:");
dog.sleep();
System.out.println("吃饭时间到:");
dog.eat();
}
从这段程序可以看出,dog类扩展了动物类的功能,但是它违背了里氏替换原则,重写了父类原有的功能,把吃的功能变成了回到狗窝的功能。这样就造成了到了吃饭时间我们调用吃饭的动作,可是却回到了狗窝。很饿。。。
怎么做?
不改变父类原有的功能就行了
class Dog extends Animal {
public void goHome() {
System.out.println("回到自己的狗窝里");
}
public void sleep() {
goHome();
System.out.println("睡觉");
}
}