简单工厂模式、单例模式以及MVC模式

本文是对简单工厂模式、单例模式、以及MVC模式的简单介绍。

至于你问,为什么把这三种模式放在一起讲,笔者目前也很懵逼,因为这是一个作业...

好了,言归正传。

一、简单工厂模式(Simple Factory)


严格的说,在23种设计模式中是没有简单工厂模式的,那么它的定位是什么呢?

在23种设计模式中,有工厂方法模式和抽象工厂模式,而一般我们提到工厂模式却有三种,再加上简单工厂模式。因为,GOF在《设计模式》一书中,将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory),而简单工厂模式被看做工厂方法模式的一种特例,将两者归为一类。
so,我们先从最简单的工厂模式——简单工厂模式说起。

1.1 模式动机


举个简单的例子,在windows的画图中,可以画圆、矩形、三角形等形状,它们都来自同一个父类,只不过在子类修改重写了具体的方法,使它们可以呈现不同的效果。如果我们希望,在实现这些形状时,不需要知道具体的形状,只需要表示该按钮的一个参数,并提供一个方便调用不同形状的方法,把参数传入该方法即可返回对应的形状对象,此时,需要用到简单工厂模式了。


1.2 模式结构



简单工厂模式包含如下角色:

1. 工厂(Factory):工厂负责实现创建所有实例的内部逻辑。
2. 抽象产品(Product):抽象产品所创建的所有对象的父类,负责所有实例所共有的公共接口。
3. 具体产品(ConcreteProduct):具体产品是创建目标,所有创建的对象都是其的具体类实例。


1.3 实例


此处,还是以形状举例。


1. 抽象产品类

public class Shape{
    public paint(){}
}

2. 具体产品类
public class Circle extends Shape{
    public paint(){
        System.out.println("画了一个圆形");
    }
}

public class Rectangle extends Shape{
    public paint(){
        System.out.println("画了一个三角形");
    }
}


3. 工厂类
public class Factory{
    public static Shape getShape(String arg){
        if(arg.equals("circle")){
            return new Circle();
        } 
        if(arg.equals("rectangle")){
            return new Rectangle();
        }
    }
}

4. 客户端
public static void main(String[] args) {
    Shape shape = Factory.getShape("circle");
    shape.paint();
}

二、单例模式(Singleton)


单例模式,顾名思义就是一个类只有一个实例。那么我们什么时候会需要用到这种模式呢?

1.1 模式动机


一个简单的例子,windows系统中的任务管理器。当我们多次点击“启动任务管理器”时,系统始终只能弹出一个任务管理器窗口,也就是说在windows系统中,任务管理器具有唯一性。为什么要这样设计呢?可以从以下两个方面考虑:1.如果能弹出多个窗口,而这些窗口都是一致的,全部都是重复的对象,势必会浪费大量系统资源,包括cpu资源及内存资源,浪费是可耻的! 2.如果弹出的多个窗口内容不一致,问题就更严重了,用户到底相信哪一个是真的呢?这不是在逗用户玩嘛~因此更加不可取。 

由此可见,确保任务管理器只有一个非常重要。这也显示了单例模式的重要性。

1.2 模式定义


单例模式,顾名思义就是类只有一个实例,另外,单例模式需要满足自行实例化并向整个系统提供这个实例。

单例模式有如下三个特点:
1.单例类只能有一个实例。
2.单例类必须自己创建自己唯一的实例。
3.单例类必须给所有其他对象提供这一实例。

接下来介绍一下单例类的两种不同的实现,饿汉式单例和懒汉式单例。


1.3 饿汉式单例


饿汉式单例是实现起来最简单的单例。


public class EagerSingleton{
    private EagerSingleton(){}

    private static final EagerSingleton single = new EagerSingleton();

    public static EagerSingleton getInstance(){
        return single;
    }
}


当类被加载时,静态变量instance会被初始化,单例类的唯一实例被创建,且以后不再改变,天生是线程安全的。


1.4 懒汉式单例

public class LazySingleton{
    private LazySingleton(){}

    private static LazySingleton single = null;

    public static LazySingleton getInstance(){
        if(single == null){
            single = new LazySingleton();
        } 
        return single;
    }
}


懒汉式在类加载时并不自行实例化,而是在第一次调用getInstance() 方法时实例化,这种技术又称为延时加载(Lazy Loader)技术,即需要的时候再加载实例。


但以上懒汉式单例并没有考虑线程安全问题,它是线程不安全的,在并发环境下很可能出现多个实例。要实现线程安全,有以下三种方式(下方代码只对getInstance函数重写,其他部分相同):


1.加上synchronized

synchronized public static LazySingleton getInstance(){
    if(single == null){
        single = new LazySingleton();
    } 
    return single;
}


此方法虽然实现了线程安全,但是每次调用getInstance() 时都要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。那么如何既能解决线程安全又能不影响系统性能呢?我们对此方法再次进行改进。


事实上,只需要创建实例的代码进行线程锁定即可,代码如下:


public static LazySingleton getInstance(){
    if(single == null){
        synchronized (LazySingleton.class){
            single = new LazySingleton();
        }
    } 
    return single;
}

问题好像被解决了,然而并没有...还是会存在单例对象不唯一的情况。

假如在某一瞬间,线程A和B都在调用getInstance() 方法,此时single为null,因此都可以进入if语句。由于实现了synchronized,保证只有一个线程在执行该代码,因此线程A继续执行,而线程B进入等待状态。当A执行完毕后,线程B又再一次创建了新的实例,导致实例不唯一,违背了单例模式的设计思想。


因此需要再次改进,在进入synchronized后再进行一次判断single是否为空,这种方式称为"双重检查锁定"(Double Check Lock, DCL)。


2. 双重检查锁定(DCL)

public class LazySingleton{
    private LazySingleton(){}

    private static volatile LazySingleton single = null;

    public static LazySingleton getInstance(){
        if(single == null){
            synchronized (LazySingleton.class){
                if(single == null){
                    single = new LazySingleton();   //① 非原子操作
                }
            }
        } 
        return single;
    }
}


注意,此处需要在变量single前加上修饰符volatile,否则,由于语句①是非原子操作,仍存在线程不安全的问题。而volatile的可见性及禁止指令的重排序优化的作用,可以避免此情况。另外需要注意的是,java 5 以前的版本,使用了volatile的双重锁还是有问题的,因为那时的volatile不能完全避免重排序。


于是,又出现了一种新的方法,既能实现实现延迟加载,而且又是线程安全的。


3. 静态内部类(static nested class)


比较推荐静态内部类的方法,这也是jdk的设计师写的《Effective java》中推荐的。

public class LazySingleton{
    private LazySingleton(){}

    private static class Nested{
        private static final LazySingleton single = new LazySingleton();
    }

    public static LazySingleton getInstance(){
        return Nested.single;
    }
}

由于Nested是私有的,只有getInstance() 可以访问,且在getInstance() 被第一次调用时才创建实例,因此它是懒汉式的,且线程安全,没有性能缺陷。


三、MVC模式


3.1 模式动机


MVC模式是为了那些需要为多个视图提供同样的数据的的应用程序而设计的,当一个模型能为多个视图提供数据,应用于模型的代码只要写一次就够了,大大降低代码的重复性。


3.1 模式介绍


MVC是Model-view-Controller(模型-视图-控制器)的缩写。它将应用程序分成三个核心部件,即模型、视图、控制器,它们各自处理自己的任务,使得应用程序的输入、处理和输出分开。


MVC的逻辑关系


(图片来自百度百科)


1. 视图(View):

用户看到并与之交互的界面。MVC的好处就是能为应用程序处理多个不同的视图,但是视图中并不对用户的操作进行处理,它只是输出数据并允许用户操作的界面。


2. 模型(Model):

模型是MVC的核心,程序需要的数据以及程序的功能都由模型提供,因此有时又叫做数据层。


3. 控制器(Controller):

接受用户的输入并调用模型和视图去完成用户的请求。控制器本身不输出任何信息,也不做任何操作,它只是请求信息并觉得调用哪个模式进行处理,然后再决定哪个视图来显示结果。


3.3 实例


1. 创建模型

public class Rectangle{

    private double wide;
    private double high;
    private double area;

    public void setWide(double w){
        this.wide = w;
    }

    public double getWide(){
        return wide;
    }

    public void setHigh(double h){
        this.high = h;
    }

    public double getHigh(){
        return high;
    }

    public double getArea(){
        return high*wide;
    }
}


2. 创建视图

public class RecView{

    //view 1
    public void printHighWide(double high,double wide){
        System.out.println("Rectangle's high is :" + high);
        System.out.println("Rectangle's wide is :" + wide);
    }

    //view2
    public void printArea(double high,double wide,double area){
        System.out.println("when Rectangle's high=" + high +" and wide=" + wide);
        System.out.println("Rectangle's area is :" + area);
    }
}

3. 创建控制器

public class RecController{


    private Rectangle model;
    private RecView view;

    public RecController(Rectangle model,RecView view){
        this.model = model;
        this.view = view;
    }

    //控制器接收请求并决定所要调用的模型
    public void setRecHigh(double high){
        model.setHigh(high);
    }

    public void setRecWide(double wide){
        model.setWide(wide);
    }

    //由控制器决定用哪个视图来显示返回数据
    public void printRecHW(){
        view.printHighWide(model.getHigh(),model.getWide());
    }

    public void printRecArea(){
        view.printArea(model.getHigh(),model.getWide(),model.getArea());
    }
}

4. 用法演示

public class MVCDemo{

    private static Rectangle setData(){
        Rectangle rec = new Rectangle();
        rec.setHigh(3);
        rec.setWide(2);
        return rec;
    }

    public static void main(String[] args) {

        Rectangle model = setData();
        RecView view = new RecView();

        RecController controller = new RecController(model,view);

        //print high and wide
        controller.printRecHW();

        //updata high
        controller.setRecHigh(5);

        //print area
        controller.printRecArea();
    }

}

5.结果输出





以上,便是本博文的全部内容。初学者,如有错误,欢迎指正!
(双手合并,放于胸前,90°鞠躬)




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值