本文是对简单工厂模式、单例模式、以及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(){}
}
public class Circle extends Shape{
public paint(){
System.out.println("画了一个圆形");
}
}
public class Rectangle extends Shape{
public paint(){
System.out.println("画了一个三角形");
}
}
public class Factory{
public static Shape getShape(String arg){
if(arg.equals("circle")){
return new Circle();
}
if(arg.equals("rectangle")){
return new Rectangle();
}
}
}
public static void main(String[] args) {
Shape shape = Factory.getShape("circle");
shape.paint();
}
二、单例模式(Singleton)
1.1 模式动机
1.2 模式定义
接下来介绍一下单例类的两种不同的实现,饿汉式单例和懒汉式单例。
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;
}
事实上,只需要创建实例的代码进行线程锁定即可,代码如下:
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°鞠躬)