@myecho
2019-03-27T11:20:13.000000Z
字数 13920
阅读 1144
Java
+代表public
-代表private
#代表protected
核心代码就是
pulic static FruitFactory(String which) {
if (which.equalsIgnoreCase("apple")) {
return new Apple();
} else if (which.equalsIgnoreCase("strawberry")) {
return new StrawBerry();
} else {
throw new BadFruitException("Bad fruit request.");
}
}
实际上有一个接口工厂类,然后有一个产品就产生一个对应的产品工厂实现类。
在Java中的应用
1. 所有的聚集对象都会有返回一个Iterator抽象接口的iterator方法,这个方法就是一个工厂方法,然后不同的聚集对象去实现自己的工厂逻辑
2. 所有继承了List接口的实现中,还包含了一个listIterator方法,也是一个工厂方法。
这种工厂方法的基础上是产品族的概念,是指在不同的产品登记结构中,功能相关联的产品构成的家族。一般而言,有多少个产品等级结构,就会在工厂角色中发现多少个工厂方法。
private static Singleton _instance;
public static Singleton getInstance() {
if (_instance == null) { //1
_instance = new Singleton(); //2
}
return _instance; //3
}
但上边这种情况不适用于多线程的情况,反例如下所示,
假设两个线程并发调用 getInstance() 方法并且按以下顺序执行调用:
1. 线程 1 调用 getInstance() 方法并决定 instance 在 //1 处为 null 。
2. 线程 1 进入 if 代码块,但在执行 //2 处的代码行时被线程 2 预占。
3. 线程 2 调用 getInstance() 方法并在 //1 处决定 instance 为 null 。
4. 线程 2 进入 if 代码块并创建一个新的 Singleton 对象并在 //2 处将变量 instance 分配给这个新对象。
5. 线程 2 在 //3 处返回 Singleton 对象引用。
6. 线程 2 被线程 1 预占。
7. 线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton 对象。
8. 线程 1 在 //3 处返回这个对象。
朴素的多线程版本,但由于每次都要加锁,因此效率不高。
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
适用于多线程的双重检测锁:
private static Singleton _instance;
public static Singleton getInstanceDC() {
if (_instance == null) { // Single Checked
synchronized (Singleton.class) {
if (_instance == null) { // Double checked
_instance = new Singleton();
}
}
}
return _instance;
}
似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B在single checked的位置进行判断,由于instance此时不是null,因此它马上离开了函数并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
可以通过将_instance实例设置为volatile变量来阻止指令重排,会在编译的字节码中加入内存屏障(读也加、写也加),jdk1.5之后有效。
synchronized只能保证代码块内的变量改变能够及时的写回去。
http://www.cnblogs.com/cookiezhi/p/5774583.html
另外一种解决当前指令重排问题的方式:(用另一个方法调用来保证不会发生指令重排的现象)
public class SingletonTest {
private static SingletonTest instance = null;
private SingletonTest() {
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new SingletonTest();
}
}
public static SingletonTest getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
}
预先加载方法
class S1 {
private S1() {
System.out.println("ok1");
}
private static S1 instance = new S1();
public static S1 getInstance() {
return instance;
}
}
唯一的缺点就是无论单例类是否被需要,都会创建一个实例出来。
升级版:
单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return getInstance();
}
}
C++中的单例模式:https://blog.csdn.net/qq_35280514/article/details/70211845
建造者通常需要完成产品的组装过程(组装可能仅是set一下或者有更复杂的处理逻辑),而不是仅仅像工厂方法一样直接返回产品类。
其实在上图中,我们可以去掉导演者的角色,然后将Builder类中的组装逻辑return this来支持链式调用,导演者其实也就是完成了链式调用的这个过程并最后返回了Student类的任务而已。
Student.builder()
.stuName("chenxuxu")
.stuAge(22)
.stuGrade("13级")
.stuMajor("软件工程")
.stuNo("123456789");
Student stu = new Student();
stu.setAge(22);
stu.setName("chenxuxu");
stu.setGrade("13级");
stu.setNo("123456789");
stu.setMajor("软件工程");
public class Student {
/**
* 不能通过new初始化
*/
private Student(){}
private String name;
private int age;
private String no;
private String grade;
private String major;
public static Builder builder(){
return new Builder();
}
//public get and set functions ...
}
class Builder {
private Student stu;
private Builder(){} //只能通过Student.builder()获得
public Builder stuName(String name){
stu.name = name; // call set function
return this;
}
public Builder stuAge(int age){
stu.age = age;
return this;
}
public Builder stuNo(String no){
stu.no = no;
return this;
}
public Builder stuGrade(String grade){
stu.grade = grade;
return this;
}
public Builder stuMajor(String major){
stu.major = major;
return this;
}
public Student create(){ //最后调用,作为链式调用的终点
return stu;
}
}
实现clone()方法要注意以下几点:
1. 对任何的对象x,都有:x.clone() != x
2. 对任何的对象x,都有x.clone().getClass() == x.getClass()
3. 如果对象x的equals方法定义恰当的话,那么x.clone().equals(x)应当是成立的
同时要注意clone()方法的深复制与浅复制的问题,
而有一些对象,比如线程thread对象或者Socket对象,是不能简单复制或者共享的。不管是使用深复制还是浅复制,只要涉及这样的对象就必须把对象设置为transient而不予复制;或者由程序自行创建相同同种的对象,权当做复制件使用。
原型模式的优点
原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
假设产品类具有一定的等级结构,如果采用工厂模式,则工厂类也必须有一个相应的等级结构,而产品类的等级结构一旦发生变化,则工厂类的等级结构也要发生变化。如果采用原型模式,则给每一个产品类都配备一个克隆方法(大多数的时候只需要给产品类等级结构的根类配备一个克隆方法),便可以避免使用工厂模式所带来的具有固定等级结构的工厂类。
原型模式的缺点
原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。
结构模式描述如何将类或者对象结合在一起形成更大的结构。结构模式描述两种不同的东西:类与类的实例,根据这一不同,结构模式可以分为类的结构模式和对象的结构模式。
几个应用实例:
1. 将Itetator适配成老java编译器所支持的Enumeration接口,使用对象的适配器模式
2. 将某个电脑游戏的Linux桌面适配成Windows桌面
一个纯粹的装饰类必须与Component对象在接口上的完全相同,并增强后者的功能。
所有实现了Sourceable接口的类都可以被Decorator类来修饰。
如果需要很多不同类型的修饰类的,都可以在继承Decorator类的基础上重写这些方法。
主要是运用了适配器模式和装饰模式。
其子类被分为了原始流处理器和链接流处理器,而实际上这些链接流处理器就是作为原始流类的装饰类而存在的。修饰了InputStream的内部工作方式。
我们可以看到与装饰模式的不同:代理模式在创建时就已经明确知道了要代理的是哪个具体类了,而装饰模式则不知道其要装饰的实现了接口的哪个具体类,因此代理模式也不需要传入实例。
合成模式把整体和部分的关系用树结构表示出来。
举例:一幅图画中所拥有的长方形、正方形、或者内嵌的图画构成了一种树形结构,建议使用合成模式。
实例:http://www.cnblogs.com/java-my-life/archive/2012/04/17/2453861.html
在Java语言中,String类型就使用了享元模式。String是不可变对象,一旦创建出来就不可改变。在JVM内部,String对象都是共享的。
享元对象能做到共享的关键是区分内蕴状态和外蕴状态。一个享元对象拥有内蕴状态并可以被共享。
内蕴状态在创建之后不能够被改变。如果一个享元对象有外蕴状态的话,所有的外部状态都必须储存在客户端,与内蕴状态相互隔离。
享元对象必须通过享元工厂对象获得(一般做成单例模式),然后判断拥有此状态的享元对象是否存在,不存在时才创建并返回。
复合享元模式指的是享元对象有可能是一种复合状态,这样的复合享元对象本身不能共享,但是它们可以分解成单纯的享元对象。
将抽象化与实现化脱耦,使得二者可以独立的变化----定义。
桥梁模式的用意是把实现和它的接口分开,以便它们可以独立的变化。要体会与适配器模式的不同。
这里要着重注意的就是上图中提到的 实现化角色与抽象化角色的接口可以非常不一样,因为实现化相当于是具体的实现,而抽象化角色是高层的抽象。抽象化角色会向实现化角色委派任务。
典型的应用就是JDBC驱动库的实现。
实例:http://www.cnblogs.com/java-my-life/archive/2012/05/07/2480938.html
外部系统与一个子系统的通信必须通过一个统一的门面对象进行,这就是门面模式。门面模式提供一个高层次的接口,使得子系统更易于使用。
* 门面(Facade)角色 :客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
* 子系统(SubSystem)角色 :可以同时有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合(如上面的子系统就是由ModuleA、ModuleB、ModuleC三个类组合而成)。每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
在Tomcat中的Request以及Response都存在与之对应的Facade类,这样做的原因是Request对象中的很多方法都是内部组件之间相互交互时使用的,比如setComet、setRequestedSessionId等方法。这些方法并不对外部公开,但是又必须设置为public,因为还需要跟内部组件之间交互使用。最好的解决方法就是通过使用一个Facade类,将与内部组件之间交互使用的方法屏蔽掉,只提供给外部程序感兴趣的方法。
行为模式是在不同的对象之间划分责任和算法的抽象化。行为模式不仅仅是关于类和对象的,而且是关于它们之间的相互作用的。
行为模式分为类的行为模式和对象的行为模式两种:
* 类的行为模式 类的行为模式使用继承关系在几个类之间分配行为。
* 对象的行为模式 对象的行为模式则使用对象的聚合来分配行为
策略模式其用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以互相替换。
经常可以见到的是,所有的具体的策略类都有一些公共的行为。这时候,就应当这些公共的行为放到共同的抽象策略角色Strategy类里面。
模板方法模式与策略模式不同在于,策略模式使用委派的方法提供不同的算法行为,而模板方法模式使用继承的方法提供不同的算法行为。而策略模式可行的基础就是里氏代换规则,同时策略模式也复合"开-闭"规则。
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。
HttpServlet类中的service方法就是一个模板方法,这个方法根据http methond的不同区调用七个do方法中的一个或者几个。子类可以重写do*()方法来实现不同的剩余逻辑。
观察者模式是对象的行为模式,又叫做发布-订阅模式或者源-监听器模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象发生变化时,会通知所有的观察者对象,使得它们能够自动更新。
另外一种观察者模式的实现方式:
Java语言本身提供了对观察者模式的支持:
在java.util库里面,提供了一个Observable类以及一个Observer接口,构成对观察者模式的支持。
其中被观察者类都是Observable类的子类
Observer只有一个update方法,提供更新操作。
public class MyCollection implements Collection {
public String string[] = {"A","B","C","D","E"};
public Iterator iterator() {
return new MyIterator(this);
}
public Object get(int i) {
return string[i];
}
public int size() {
return string.length;
}
}
public class MyIterator implements Iterator {
private Collection collection; //持有实例是最关键的一步
private int pos = -1;
public MyIterator(Collection collection){
this.collection = collection;
}
public Object previous() {
if(pos > 0){
pos--;
}
return collection.get(pos);
}
public Object next() {
if(pos<collection.size()-1){
pos++;
}
return collection.get(pos);
}
public boolean hasNext() {
if(pos<collection.size()-1){
return true;
}else{
return false;
}
}
public Object first() {
pos = 0;
return collection.get(pos);
}
}
以上的实现方式叫做白箱聚集与外禀迭代子。
一个黑箱聚集不向外部提供遍历自己元素对象的接口,因此,这些元素对象只可以被聚集内部成员访问。由于内禀迭代子恰好是聚集内部的成员子类,因此,内禀迭代子对象是可以访问聚集的元素的。
主要区别在于以下:
其中Aggregate接口只定义了一个createIterator()方法。Iterator接口与白箱模式相同。
使用外禀迭代子的重要理由是它可以被几个不同的聚集一起使用,只要他们有相同的抽象接口。而内禀迭代子的优点是它不破坏对象聚集的封装(可以保证在迭代过程中不会受到用户调用遍历元素接口的影响)。
在java中的实现要注意以下几点:
1. 虽然在java中的聚集中提供了对外访问数据的接口,但是java仍然采用的内禀迭代子的实现,有的是内部类、甚至是匿名类的实现。
2. java中的迭代器也不只一种,例如listIterator\ArrayDeque的descendingIterator等,在使用时最好打开源码看一下。
3. Fail fast机制,当聚集在迭代时,如果发现其他线程修改了此集合,立即抛出ConcurrentModificationException异常,其判断机制是通过modCount来记录改变集合结构的操作次数而实现的。当expectedModCount != modCount时,则抛出异常。
责任链模式是对象的一种行为模式。在责任链中,很多对象由每一个对象对其下家的引用而链接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。
其中Handler是一个抽象类。源码可定义如下:
public abstract class Handler
{
protected Handler successor;
public abstract void handleRquest();
public void setSuccessor(Handler successor){
this.successor = successor;
}
public Handler getSuccessor() {
return successor;
}
}
命令模式属于对象的行为模式,命令模式又称行动模式或交易模式。命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。
具有以下优点:
(1) 使得新的命令很容易的被加入系统中
(2) 允许接受请求的一方决定是否要否决请求
(3) 能比较容易的设计一个命令队列
(4) 可以容易的实现对请求的Undo和Redo
(5) 在需要的情况下,可以较容易的将命令记入日志
如果需要支持undo和redo操作的话,需要保存以下状态信息 (1)(2)用于redo (3)用于undo
(1) 接受者对象实际上请求所代表的操作
(2) 对接受者对象所作操作所需要的参数
(3) 接受者类最初的状态
与备忘录模式的关系:
如果命令需要撤销或者恢复操作的话,备忘录模式可以用来储存关于命令的状态效果信息。
备忘录模式又叫做快照模式,是对象的行为模式。
备忘录对象是一个用来存储另外一个对象内部状态的快照。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而可以再将来合适的时候把这个对象还原到存储起来的状态。
我们把存储这些快照的备忘录对象叫做对象的历史,某一个快照所处的位置叫做检查点。
备忘录模式的白箱模式,此时备忘录对象对于负责人对象来说是透明的,可以设置备忘录的对象。
public class Original {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Original(String value) {
this.value = value;
}
public Memento createMemento(){
return new Memento(value);
}
public void restoreMemento(Memento memento){
this.value = memento.getValue();
}
}
public class Memento {
private String value;
public Memento(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public class Storage {
private Memento memento;
public Storage(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
备忘录对象的黑箱模式,此时备忘录对象对发起人和负责人来说提供了不同的使用接口,相当于提供了双重接口。
这里的Context环境类就是TcpConnection类。
如何区分状态模式与策略模式?
考察环境角色是否有明显的状态的改变。如果环境角色只有一个状态,那么就使用策略模式,策略模式的特点是一旦环境选定一个具体策略类,那么整个环境类的生命周期内都不会改变这个具体的策略类。而状态模式则适用于环境角色中状态不断发生改变的情况。
访问者模式是对象的行为模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于数据结构上的操作之间的耦合解开,使得操作集合可以相对自由的变化。
public interface Visitor {
/**
* 对应于NodeA的访问操作
*/
public void visit(NodeA node);
/**
* 对应于NodeB的访问操作
*/
public void visit(NodeB node);
}
public class VisitorA implements Visitor {
/**
* 对应于NodeA的访问操作
*/
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/**
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}
}
public class VisitorB implements Visitor {
/**
* 对应于NodeA的访问操作
*/
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/**
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}
}
public abstract class Node {
/**
* 接受操作
*/
public abstract void accept(Visitor visitor);
}
public class NodeA extends Node{
/**
* 接受操作
*/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* NodeA特有的方法
*/
public String operationA(){
return "NodeA";
}
}
public class NodeB extends Node{
/**
* 接受方法
*/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* NodeB特有的方法
*/
public String operationB(){
return "NodeB";
}
}
//结构对象角色类,这个结构对象角色持有一个聚集,并向外界提供add()方法作为对聚集的管理操作。通过调用这个方法,可以动态地增加一个新的节点。
public class ObjectStructure {
private List<Node> nodes = new ArrayList<Node>();
/**
* 执行方法操作
*/
public void action(Visitor visitor){
for(Node node : nodes)
{
node.accept(visitor);
}
}
/**
* 添加一个新元素
*/
public void add(Node node){
nodes.add(node);
}
}
我们可以看到在实现访问模式过程中实现了两次分派过程,第一次是node.accept(Visitor)是一次分派,第二次是accept方法内部,visitor.visit(this)又是一次分派的过程。
先进行一次静态分派,再根据invokevirtual指令进行动态分派。
解释器模式是类的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一种解释器。客户端可以使用这个解释器来解释这个语言中的句子。
调停者模式是对象的行为模式。调停者模式包装了一系列对象相互作用的方式,使得这些对象之间不必互相明显引用,从而使它们可以较松散的耦合。 是迪米特法则体现比较明显的一个设计模式。
调停者在创建时就确定好了要调停的是哪几个同事。setColleagueChanged(Colleague)是当某个同事发生变化时,其他同事应当采用的action操作。