1.简单工厂模式:
简单工厂模式属于类的创建型模式,又叫做静态工厂模式。通过专门创建一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
模式中包含的角色及其职责:
1.工厂角色。
简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
2.抽象角色。
简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
3.具体产品角色。
简单工厂模式所创建的具体实例对象。
然后直接来看代码进行理解:
public interface Fruit {
public void get();
}
public class Apple implements Fruit {
@Override
public void get() {
System.out.println("获得苹果");
}
}
public class Banana implements Fruit {
@Override
public void get() {
System.out.println("获得香蕉");
}
}
public class FruitFactory {
public static Fruit getFruit(String fruit) throws InstantiationException, IllegalAccessException {
Fruit result = null;
if (fruit.equalsIgnoreCase("apple")) {
result = Apple.class.newInstance();
} else if (fruit.equalsIgnoreCase("banana")) {
result = Banana.class.newInstance();
} else {
System.out.println("找不到要实例化的类");
}
return result;
}
}
public class MainClass {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Fruit fruit = FruitFactory.getFruit("apple");
fruit.get();
fruit = FruitFactory.getFruit("banana");
fruit.get();
}
}
视频里用了两种反射方法生成实例的,一种是像上面那样,Apple.class.newInstance();
一种是Class fruit=Class.forname(fruit); (Fruit)fruit.newInstance();
第二种方法要求用户填入的参数就是类名,否则就会报错,这样对用户的要求就比较高。但是这样的话可扩展性好,没有了逻辑判断,只要输入参数所代表的类实现了Fruit这个接口就行了。
第一种方法则是带有判断逻辑,所以容错率高。但是代码比较麻烦,可扩展性低。一般来说选第二种比较好。
然后大家可能会想到为啥不用new关键字直接生成对象?
newInstance: 弱类型。低效率。只能调用无参构造。
new: 强类型。相对高效。能调用任何public构造。
详细的大家可百度一下两者的区别。
从上到下看分别是水果的接口,具体的苹果类,香蕉类,水果工厂,主类。
然后我们来对应前面说的结构:
1.工厂角色对应的就是水果工厂类,负责创建各种水果的实例。它使用静态方法,根据传入的参数来确定生成哪个具体产品的实例。包含判断逻辑。
2.抽象角色就是水果的接口,它描述了所有实例的公有的接口。定义了水果的公共方法。
3.具体产品角色就是苹果类和香蕉类,实现了水果的接口。是具体的实现。
总体思想就是类的生成交由工厂来处理,而不是直接在主类中Fruit apple=new Apple();
简单工厂模式的优缺点:
优点:工厂类是这个模式的关键,它包含必要的判断逻辑,能根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
也就是说,对于用户来讲,你不需要去了解具体的水果类和接口,只需要了解这个水果工厂,知道传入什么样的参数就会返回什么样的结果就ok了。也就是具体的水果类和接口对用户而言都透明化了。
缺点:由于工厂类把所有类的实例创建工作都包揽了下来,所以就不太符合软件工程中说的”高内聚“的原则(也就是原则上是尽量分开的)。还有就是由于工厂类要对输入参数进行相应的判断,所以扩展性不好。比如你要增加一个西瓜类的实例创建,那么此时你就必须要修改工厂类的代码,把对西瓜类的判断加上去。这样就要修改原来的代码了,所以扩展性不好。
2.工厂方法模式:
工厂方法模式同样属于类的创建型模式,又被称为多态工厂模式。工厂方法模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
然后来看具体的代码:
public interface Fruit {
public void get();
}
public interface FruitFactory {
public Fruit getFruit();
}
public class Apple implements Fruit {
@Override
public void get() {
System.out.println("获得苹果");
}
}
public class AppleFactory implements FruitFactory {
@Override
public Fruit getFruit() {
return new Apple();
}
}
public class Banana implements Fruit {
@Override
public void get() {
System.out.println("获得香蕉");
}
}
public class BananaFactory implements FruitFactory {
@Override
public Fruit getFruit() {
return new Banana();
}
}
public class MainClass {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// 获得苹果工厂
FruitFactory ff = new AppleFactory();
Fruit apple = ff.getFruit();
apple.get();
// 获得香蕉工厂
FruitFactory ff2 = new BananaFactory();
Fruit banana = ff2.getFruit();
banana.get();
}
}
上面的代码从上到下依次是:
水果接口(定义具体水果),水果工厂接口(生产水果),苹果类,苹果工厂类(生成苹果实例),香蕉类,香蕉工厂类,主类。
和前面的简单工厂模式相比,我们发现这个水果工厂类变成了接口,写得很简单了。然后多了两个类,分别是苹果的工厂类和香蕉的工厂类,生成实例对象的工作由水果工厂类转移到了具体的工厂类。
然后我们来看工厂方法模式中包含的角色及其职责:
1.抽象工厂角色
工厂方法模式的核心,任何工厂类都必须实现这个接口。
2.具体工厂角色
具体工厂类是抽象工厂的一个实现,负责实例化产品对象。
3.抽象角色
工厂方法模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口(在上面的例子中就是水果接口)
4.具体产品角色
工厂方法模式所创建的具体实例对象。
相信大家很容易就对应好了上面的具体的类,也是非常好理解的。
之前是产生一个工厂就直接创建实例,现在是创建了工厂可以先放在那里,等调用方法了再去产生实例。也就是前面说的将实际创建工作推迟到子类去。
然后我们再来看可扩展性:
简单工厂模式要修改水果工厂类,扩展性不好。而工厂方法模式只需要你创建具体对象和具体的工厂对象,实现相应的接口就行了。这样就不需要修改原来的代码,而且可扩展性较好。也就是很好的符合了”开放-封闭原则“ (OCP open close principle) :开放是指可扩展性好。封闭是指不用修改原来的代码。
3.抽象工厂模式
抽象工厂模式是所有形态的工厂模式中最为抽象和最其一般性的。抽象工厂模式可以向客户端提供一个接口,使得客户端不必指定产品的具体类型的情况下,能够创建多个产品族的产品对象。
感觉这个说的话有点不太好理解,还是来看代码理解最快。
public interface Fruit {
public void get();
}
public abstract class Apple implements Fruit {
@Override
public abstract void get();
}
public class NorthApple extends Apple {
@Override
public void get() {
System.out.println("获得北方苹果一个");
}
}
public class SouthApple extends Apple {
@Override
public void get() {
System.out.println("获得南方苹果一个");
}
}
public abstract class Banana implements Fruit {
@Override
public abstract void get();
}
public class NorthBanana extends Banana {
@Override
public void get() {
System.out.println("获得北方香蕉一个");
}
}
public class SouthBanana extends Banana {
@Override
public void get() {
System.out.println("获得南方香蕉一个");
}
}
public interface FruitFactory {
public Fruit getApple();
public Fruit getBanana();
}
public class NorthFactory implements FruitFactory {
@Override
public Fruit getApple() {
return new NorthApple();
}
@Override
public Fruit getBanana() {
return new NorthBanana();
}
}
public class SouthFactory implements FruitFactory {
@Override
public Fruit getApple() {
return new SouthApple();
}
@Override
public Fruit getBanana() {
return new SouthBanana();
}
}
public class MainClass {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// 获得北方工厂
FruitFactory ff = new NorthFactory();
Fruit northApple = ff.getApple();
northApple.get();
Fruit northBanana = ff.getBanana();
northBanana.get();
// 获得南方工厂
FruitFactory ff2 = new SouthFactory();
Fruit southBanana = ff2.getBanana();
southBanana.get();
Fruit southApple = ff2.getApple();
southApple.get();
}
}
上面的代码比较多,但是都非常简单,大家理清结构就好。
从上到下分别是:
水果接口,实现水果接口的抽象的苹果类,继承苹果类的北方苹果类,继承苹果类的南方苹果类。实现水果接口的抽象的香蕉类,继承香蕉类的北方香蕉类,继承香蕉类的南方香蕉类。
水果工厂接口,实现水果工厂接口的北方工厂,实现水果工厂接口的南方工厂,主类。
简单说就是:水果分北方和南方,也就是产生了分类。我们现在的工厂是根据类别来制作,比如南方工厂只生产南方的水果。注意,这里借助了抽象类。
然后来看抽象工厂模式中的角色及其职责:
1.抽象工厂角色
抽象工厂模式的核心,包含对多个产品结构的声明,任何工厂类都必须实现这个接口。(也就是上面的水果工厂接口,声明了多种水果的获得)
2.具体工厂角色
具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品对象。(也就是上面的北方和南方工厂)
3.抽象角色
抽象模式所创建的所偶对象的父类,它负责描述所有实例所共有的公告接口。(也就是上面demo的苹果和香蕉的抽象类)
4.具体产品角色
抽象模式所创建的具体实例对象。(具体的实例对象,如上面的北方香蕉)
相信大家对都能找到其对应的关系。
我们来仔细分析这个抽象工厂模式:
如果要增加温室工厂,那就实现水果工厂接口,然后产生具体的实例对象即可,所以在这个方面的扩展就复合 开放封闭原则(OCP)。
可是,如果要增加水果种类,比如增加个西瓜,那么我们就要修改水果工厂里的方法,然后全部都要做出修改,这样就不符合OCP原则了。
所以,抽象工厂模式的优缺点大家都了解啦。具体使用的话还是得看实际场合来决定。
学习了上面的三种设计模式,大家注意到简单工厂模式又叫做静态工厂模式,因为核心的工厂类定义的都是静态方法,不需要生成实例对象即可调用。 工厂方法模式又叫做多态工厂模式,因为它到处都是多态。
不知道大家还记得面向对象技术的三大特征之一 :多态性吗?
多态分为静态多态和动态多态,静态多态就是一个类内有多个同名的方法,但是方法的参数类型或者参数的排列顺序不同,使调用同名方法可以产生不同的结果。
我们常说的多态一般是指动态多态性。也就是父类引用指向子类实例,比如前面的demo中,我们都是 Fruit apple =new Apple(); 而Apple类实现了Fruit的接口。 这就是多态了,一般的话我们是 Apple apple =new Apple(); 通过动态多态性使得可以通过父类型的引用调用子类型的成员方法。 也就是说这里存在类型的转换,这就导致了无法在编译时识别一个引用表达式所指向的实例对象是该引用表达式的类型的实例对象,还是其子类型的实例对象,所以在动态多态性的方法中无法在编译时识别具体调用的成员方法,而这一般需要在运行时才能被jvm识别。
由于直接调用的是子类方法,所以如果要调用父类的方法,就得在子类中使用super关键字。
这里要注意的是动态多态性只针对非静态的成员方法。
上面只多态在代码上的理解,然后我们在实际业务中进行理解。
举个在传智博客中看到的例子:
public abstract class Animal {
abstract void eat();
}
public class Cat extends Animal {
@Override
void eat() {
System.out.println("cat is eating");
}
public void photo() {
System.out.println("cat is photoing");
}
}
public class Dog extends Animal {
@Override
void eat() {
System.out.println("dog is eating");
}
public void lookHome() {
System.out.println("dog is looking home");
}
}
public class MainClass {
public static void main(String[] args) {
Animal aDog = new Dog();
Animal aCat = new Cat();
eat(aDog);
eat(aCat);
}
public static void eat(Animal animal) {
animal.eat();
}
}
大家主要看最后面的主类,这就是使用多态的好的例子,理解起来也比较好。如果主类中的eat传入的参数是dog 或者cat,那么针对每一种动物都得写一个方法,代码就有很多重复,既然每一种动物都会吃东西,那么久应该往上一层把吃的这种行为封装起来,即面向动物。这样即使你再来只猪要调用这个方法,只要实现动物类就好,那么代码的复用性就很好了。不用写那么多重复的代码。提高了程序的可扩展性。
那么,同时,多态也有弊端,比如前面上面的代码中,你就不能调用adog的lookhome方法,也就是说不能调用子类特有的方法。因为它是父类型的引用。
Animal aDog = new Dog(); 这是一种类型转换,叫向上转型,也就是多态的本质。
它的好处就是上面说的扩展性好,面向一个单独方面进行使用。隐藏了子类型。
然后如果要使用子类型的特有的方法,就得向下转型,这就叫强制转型了。
Dog a=(Dog)aDog; 这样就可以使用子类的方法了,但是这里可能会出现问题,如果你把它强制转型为cat,编译时也是可以通过的,因为cat也是实现了Animal类。但是运行的时候就会报强制转型错误的异常。
所以这个时候要使用 instanceof 关键字先判断是否是dog类型,然后再进行强制转型,这样就不会出现异常了。
嗯,感觉就说到这里吧,后面慢慢加文章说明这些设计模式在各种框架和源码中的使用。