1.模板模式
模板设计模式简单理解就是模板化,标准化,也算解耦合的一种形式。比如有两个类都实现了一个接口,比如学生类和老师类,都继承了清华大学的类,那么怎区别他们使老师还是学生呢,很简单,让学生和老师类都有一个type方法,表明自己的身份,向外介绍的时候就说”我是清华大学的老师或者我是清华大学的学生“,这里面”我是清华大学的”就是一个模板,后边老师和学生就是他们自己特有的方法。那么模板就可以提出来放到清华大学类里边,成为公用的,而具体的老师和学生就需要具体实现的,那么我需要三个类,清华大学,老师,学生。
abstract class qinghua{
public void show(){
System.out.println("我是清华大学的"+getType());//模板
}
abstract String getType();//需要实现的
}
class teacher extends qinghua{
@Override
String getType() {
return "老师";
}
}
class student extends qinghua{
@Override
String getType() {
return "学生";
}
}
public class Demo {
public static void main(String[] args) throws IOException {
student s =new student();
s.show();
}
}
打印“我是清华大学的学生”。所以模板模式其实就是在父类中进行了抽取。结构是非常简单的
图例:
2.装饰模式(包装模式)
装饰模式就是对某个类进行包装,目的是改写已存在的类的某个方法或某些方法,那么有一个例子是改写jdbc链接池链接connection的关闭方法。在以前开发有手写链接池的,连接池获取连接,链接用完后应该放回到池中而不是关闭掉,如果按照原始的DriverManager.getConnection()得到的connection是MySQL驱动实现好的子类,因为connection本身是接口,只提供了标准方法,具体实现都是子类,那么close方法就是直接关闭掉,但我们不想关闭掉,想把链接放回池去,所以要改写connection的close方法,但是又不能影响其他的方法,connection的具体子类我们又没有源码,怎么办呢?这就用到了装饰模式。
//步骤:
/*1、编写一个类,实现与被包装类相同的接口。(具备相同的行为)
2、定义一个被包装类类型的变量。
3、定义构造方法,把被包装类的对象注入,给被包装类变量赋值。
4、对于不需要改写的方法,调用原有的方法。
5、对于需要改写的方法,写自己的代码。*/
public class MyConnection implements Connection {
private Connection con;
private LinkedList<Connection> pool;
public MyConnection(Connection con, LinkedList<Connection> pool) {
this.con = con;
this.pool = pool;
}
@Override
public void close() throws SQLException {
pool.addLast(con);
}
@Override
public Statement createStatement() throws SQLException {
return con.createStatement();
}
那么我们看MyConnection,这个类实现了connection接口,实现了它所有的方法,并改写了close方法。我们把具体的connection子类通过构造方法传进来,并使用它实现其他方法。最后完成了对close的包装。这和代理模式是有些像的。
图例:
3.适配器模式
适配器就是作为调用者和拥有者之间的桥梁,调用者想调用的和拥有者能给的并不一致,所以需要中间人进行协调,这里的问题是,那为什么不重新写一个呢,一定要调用吗,所以适配器并不是在设计时就定好的,而是在开发中因为需要将一个功能放到新环境中时,与新环境不能兼容而需要修改的时候才用的。因为我们不想改变原有的类,但是又需要它适用新环境。比如手机上的存储卡,需要能在电脑上读取,就需要读卡器,再比如,电源是220v的,但是我的电脑用的是110v的,怎么办?不能把电线改成110v的吧,所以有转换器,将220v转换成110v,这就是适配器。
首先看一下java里边的例子,监听器的适配器,在java的GUI中,有一个窗口类Frame,可以创建一个窗口,但是窗口的关闭按钮不能用,必须给按扭添加关闭事件,而在java中是通过事件监听器对关闭来设置的。代码如下:
public static void main(String[] args) throws IOException {
Frame f =new Frame();
f.setBounds(100,200,300,300);
f.addWindowListener();//此处参数需要添加一个监听器,但是监听器是接口,不能实例化,所以需要我们自定义实现并重写相关方法
}
那么我实现一下windosListener
public class MyListener implements WindowListener {
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
}
发现实现后需要超多方法需要重写,但是我们不需要这些,我们只需要其中的一个windowClosing方法也就是关闭,当然其他我们可以什么都不做,但是这么多方法都是冗余的并不是我们想要的,怎么办呢?
我们可以再写一个抽象类,实现windosListener并重写那些方法,再用我们自定义的监听器继承这个抽象类,因为抽象类已经将方法全部实现了,我们的监听器只需要挑选自己想要的就可以了,(上边的装饰模式中,Myconnection也是要重写很多方法,也可以增加个dapter,但是dapter也要重写很多方法,所以显得多此一举)
public class MyListener extends MyAdater {
@Override
public void windowClosing(WindowEvent e) {
}
}
这里MyAdater就是一个中间人,适配器。再java中,也提供了一个写好的适配器WindowAdapter,所以直接使用就可以,不用自己实现了
public static void main(String[] args) throws IOException {
Frame f =new Frame();
f.setBounds(100,200,300,300);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
}
});
}
我们点进去看一下源码,发现和我们的MyAdater一样,实现了WindowListener接口,当然还实现了其他接口,使其适用范围更广。
我们再来看另一个例子,我们现在的手机都只有一个typec接口,是没有耳机接口的,那么我们想插耳机怎么办呢,可以用一个转接线就可以了。
这里要明确几个对象
1.目标接口:或者叫标准接口,我们期望的接口。比如耳机接口
2.被适配对象:现有的类,但是和我们要的不兼容。比如手机接口
3.适配器:可以将上面两个合在一起的类,转接线
public class TypeC {
//现存接口,需要适配的类
public void typcIO(){
System.out.println("typec接口");
}
}
public interface Omtp {
public void omtpIo();//耳机能使用的接口,omtp接口
}
public class TypeCAndOmtpAdapter extends TypeC implements Omtp{
@Override
public void omtpIo() {//改写目标接口,接上现有类的接口
System.out.println("可以链接typec的omtp接口");
this.typcIO();
}
}
使用的时候,直接使用适配器类就可以了。
public static void main(String[] args) throws IOException {
Omtp erji =new TypeCAndOmtpAdapter();
erji.omtpIo();
}
到这里,我们就可以使用适配器调用我们期望的方法了,可以使用我们现有的逻辑。相当于我们用typec接口接上了圆孔的耳机。
但是如果我们的typc也不同呢,一个使安卓的,一个是苹果的,我们要求这个连接线都可以连,怎么办呢,我们把适配器改一下。
public class TypeCAndOmtpAdapter implements Omtp{
private TypeC typeC;
public TypeCAndOmtpAdapter(TypeC typeC) {
this.typeC = typeC;
}
@Override
public void omtpIo() {//改写目标接口,接上现有类的接口
System.out.println("可以链接typec的omtp接口");
typeC.typcIO();
}
}
把typc定义为接口,另外创建两个实现类
public class TypeCApple implements TypeC{
@Override
public void typcIO() {
System.out.println("苹果的接口");
}
}
public class TypeCAndriod implements TypeC{
@Override
public void typcIO() {
System.out.println("安卓的接口");
}
}
我们把适配器升级了,使用构造方法把我们现有的类传进来,再使用也可以。
以上三个例子其实就是适配器模式的三种类型,第一个是接口适配器,第二个是类适配器,第三个是对象适配器,后两个其实是一种,只是实现手法有一些区别。
适配器模式和装饰模式的区别:
从适配器的对象适配器来看,和装饰模式的实现手法很像。但是他们的目标是有区别的
装饰模式:目标和自定义装饰类实现的是同一个接口,拥有的行为和目标类一样,只是要改写目标类某些方法,变成自己的方法,完成包装。
适配器模式:目标和现存类或接口并不一致,也不是同一个类型,适配器只是在中间转换,把两者都拿过来,使两者合作,完成转换。
4.工厂模式
工厂模式,顾名思义就是生成东西的地方,在Java中就是生产类的地方,这是创建型模式,往往是当你的某些对象要分门别类,但是又相互关联,在创建的时候经常需要做出不同的选择,为了降低耦合,我们应该尽量抽取创建的行为,集中到工厂中,于是就有了工厂模式。
1.简单工厂
简单工厂很简单,就是最常用的抽取。还用猫狗动物举例,实例化一个猫或者狗类,平时用new,抽取后将new的工作集合到工厂中
public class AnimalFactory {
public static Animal getAnimal(String type){
Animal a =null;
if("Cat".equals(type)){
a= new Cat();
}else if("Dog".equals(type)){
a= new Dog();
}
return a;
}
}
使用:
public static void main(String[] args) throws Exception {
Animal cat = AnimalFactory.getAnimal("Cat");
cat.eat();
}
这种方式将new Cat和new Dog抽取出来,优点是清楚简单,代码量少,缺点是必须拥有修改工厂类源码的权限,开发我们自己编写的工厂一般是这样的。
2.工厂方法模式
如果我们不能修改工厂源码怎么办呢,这时工厂的设计就需要向外扩展,利用java多态的特性,对工厂方法进行扩展,创建猫工厂和狗工厂,并重写总工厂的方法。
public interface AnimalFactory {
public Animal getAnimal();
}
public class CatFactory implements AnimalFactory {
@Override
public Animal getAnimal() {
return new Cat();
}
}
public class DogFactory implements AnimalFactory {
@Override
public Animal getAnimal() {
return new Dog();
}
}
使用:
public static void main(String[] args) throws Exception {
AnimalFactory factory =new DogFactory();
Animal dog = factory.getAnimal();
dog.eat();
}
这种方法就可以不改AnimalFactory,但是每次增加一个新类就需要同时增加一个新的具体工厂。比如想增加一个pig,就需要写一个pig工厂。
优点是对外扩展方便,不需改源码。
缺点是代码量大,比较麻烦。
图例;
3.抽象工厂模式
以上两种工厂都是创建一种动物,比如狗,猫,猪等。那么如果我想创建白猫白狗,黑猫黑狗呢,或者除了创建猫狗,还创建鸡鸭呢。猫狗属于一类,鸡鸭属于另一类,但是都是动物,按照前面的方式应该是两个不同的工厂,宠物工厂和家禽工厂,但他们又都是动物,所以应该都属于动物工厂,所以抽象工厂模式就是"工厂的工厂"模式。
假如我现在有两个简单工厂模式,PetFactory(宠物工厂)和PoultryFactory(家禽工厂),怎么把他们抽取到一个AnimalFactory中呢,我们可以使用工厂方法模式的形式,让两个工厂继承一个抽象工厂AnimalFactory,重写其中的方法。
PetFactory工厂和相关类:
public class PetFactory extends AnimalFactory{
public Pet getPet(String type){
Pet p =null;
if("cat".equals(type)){
p =new Cat();
}else {
p =new Dog();
}
return p;
}
@Override
public Poultry getPoultry(String type) {
return null;
}
}
public interface Pet {
public void eat();
}
public class Cat implements Pet {
@Override
public void eat() {
System.out.println("猫的吃方法");
}
}
public class Dog implements Pet{
@Override
public void eat() {
System.out.println("狗的吃方法");
}
}
PoultryFactory工厂和相关类
public class PoultryFactory extends AnimalFactory{
public Poultry getPoultry(String type){
Poultry poultry =null;
if("chicken".equals(type)){
poultry =new Chicken();
}else {
poultry =new Duck();
}
return poultry;
}
@Override
public Pet getPet(String type) {
return null;
}
}
public interface Poultry {
void eat();
}
public class Chicken implements Poultry {
@Override
public void eat() {
System.out.println("小鸡吃米");
}
}
public class Duck implements Poultry{
@Override
public void eat() {
System.out.println("鸭子会游泳");
}
}
者两个工厂都继承了AnimalFactory
public abstract class AnimalFactory {
public abstract Pet getPet(String type);
public abstract Poultry getPoultry(String type);
}
到这里我们已经完成了抽象工厂,可以使用工厂方法模式那样的创建方式使用AnimalFactory,但按照惯例一般会再创建一个工厂提供者,将创建具体工厂的操作再用简单工厂模式的形式包装起来。提供工厂的工厂类
public class FactoryProducer {
public AnimalFactory getAnimalFactory(String type){
if("pet".equals(type)){
return new PetFactory();
}else if("poultry".equals(type)){
return new PoultryFactory();
}
return null;
}
}
使用;
public static void main(String[] args) throws Exception {
AnimalFactory f = FactoryProducer.getAnimalFactory("poultry");
Poultry duck = f.getPoultry("duck");
duck.eat();
}
这种工厂模式比较复杂,但其实也是基于简单工厂和工厂方法模式的结合,进一步进行抽取。
图例:
5.单例模式
单例模式用的地方很多,所谓单例就是一个实例,那么要达到这个目的,我们平时的代码中就有一种简单方式,比如在一个类中定义一个static静态成员变量,这样就可以实现共享的目的,也就是所有对象都共享这一个变量。
单例模式也是同样的原理,但是为了让一个类只有一个对象,并且被所有对象共享,除了被静态,还有一点是不能被随便实例化,只能被实例化一次,这里有一个关键点:私有化构造方法,只有构造方法私有化后,别人就不能new了。剩下的就是创建方式,有几种 创建方式。
1.饿汉式
public class SingleObj {
private SingleObj() { }//构造方法私有化,非常关键
private static SingleObj singleObj =new SingleObj();//定义一个静态对象,并直接实例化
public static SingleObj getInStance(){//通过公共方法对外提供我们实例化的对象。
return singleObj;
}
}
这种方式最常用,也比较简单,在java中Runtime类就是用的这种方式,这种方式缺点就是占一定的内存,尤其是你的对象很大的话,因为一上来就要实例化一个对象,占空间。但是以现在的电脑配置来讲如果需要单例模式的地方不多的情况下,无伤大雅,所以用的很多。
2.懒汉式
public class SingleObj {
private SingleObj() { }//构造方法私有化,非常关键
private static SingleObj singleObj ;//定义一个静态变量,且不给实例对象,默认为null
public static synchronized SingleObj getInStance(){//通过公共方法对外提供我们实例化的对象。
if(singleObj==null){
return singleObj;
}
return singleObj;
}
}
懒汉式的“懒”取自“lazy loading”的lazy,也就是懒加载,懒加载就是用的时候再实例化,这样可以解决一上来就实例化占用空间的问题,但是getInStance方法在被多线程调用的时候有同时访问到singleObj为null的情况,所以该方法需要加上synchronized关键字,但是这样一来,多线程情况下性能就受到了影响。这也是其缺点。
这两种方式就是最常用的两种基本形式。除了这两种,还有很多其他的实现方式,比如双检索方式、静态内部类式(登记式)、枚举式等。可以参考其他博客。