Ericson's blog Time is limited, To be a better better man

简单工厂模式

简单工厂模式是属于创建型模式,又叫做静态工厂方法模式。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。其中包括三个角色:

  • 工厂角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象
  • 抽象产品对象角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口
  • 具体产品角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例

其UML图所下所示:
简单工厂模式示意图

简单工厂模式优点:工厂类是整个模式的关键。包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。

缺点:由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求。这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利。

使用场景:

  • 工厂类负责创建的对象比较少
  • 客户只知道传入工厂类的参数,对于如何创建对象不关心
  • 由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用

案例:计算器开发:

如果不使用简单工厂模式,就直接对+,-,*,/等操作实现一个方法;而是用简单工厂模式,可以把界面显示和操作分开为两个类,在操作中,抽象一个操作类,把+,-,*,/等操作设计为该操作类的子类来实现,在主类中通过传入的参数来使用工厂创建对应的类,这样就实现了面向对象的封装、继承和多态特性,复合简单工厂模式。

设计模式六大原则

##单一职责模式:

就一个类而言,应该仅有一个引起它变化的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,当变化发生时,设计会遭受意想不到的破坏。事实上,我们完全可以找出来进行分类,分离。软件设计真正要做的许多内容,就是发现职责相互分离。其实要去判断是否应该分离出类来,那就是如果你能够想到多于一个的动机去改变一个类,那么这个类具有多于一个的职责,应该考虑职责分离。
问题由来:类T负责两个不同的职责:职责P1,职责P2.当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
遵循单一职责原则,分布建立两个类T1、T2,使T1完成P1,T2完成P2,这样修改T1时,不会使职责P2发生故障。

##开放-封闭原则:

开闭原则是说软件实体(类、模块、函数等等)应该可以扩展,但是不可以修改。无论模块多么封闭,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对那种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离这些变化。面对需求,对程序的改动是通过增加新代码来实现的,而不是更改现有的代码,这就是开闭原则的精神所在。

##依赖倒转原则:

就是说抽象不应该依赖细节,细节应该依赖抽象。主要就是要针对接口编程,而不是针对细节编程。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成,这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构以细节为基础搭建起来的架构要稳定的多。核心思想是面向接口编程。

  • 低层模块尽量都要有抽象类或接口,或者两者都有
  • 变量的声明类型尽量是抽象类或接口
  • 使用继承时遵循里氏替换原则

##里氏替换原则:

子类型必须能够替换掉他的父类型。只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。正是由于子类型可替换性才使得父类类型模块在无需修改的情况下就可以扩展,才使得开放-封闭原则成为了可能。
问题由来:有一功能P1,由类A完成。现在需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
里氏替换比较通用的做法是:原来父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类中可以增加自己特有的方法
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更加宽松
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

##迪米特原则:

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某个方法的话,可以通过第三者转发这个调用。迪米特原则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要别的类知道的字段或行为就不要公开。迪米特原则的根本思想,是强调了类之间的松耦合。在程序设计时,类之间的耦合越弱,越利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及,也就是说,信息的隐藏促进了软件的复用。
问题由来:类与类之间关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方案:尽量降低类与类之间的耦合。
迪米特法则又叫最少知道原则,就是一个类对自己依赖的类知道的越少越好。迪米特法则只与直接朋友通信,避免与非直接的类通信,通过一个“中介”来发生联系。

##接口隔离原则:

客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。问题:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小的接口,则类B和类D必须去实现他们不需要的方法。
接口隔离未实现 隔离后的实现如下图所示: 接口隔离实现 接口隔离的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。单一职责原则注重的是职责的分离,而接口隔离注重的是接口依赖的隔离。

接口隔离要注意一下几点:

  • 接口尽量小,但要有度,如果过小,会造成接口数量过多,设计复杂化
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,隐藏它不需要的类。只有专注的为一个模块提供定制服务,才能建立最小的依赖关系
  • 提高内聚,减少对外交互,使接口用最少的方法完成最多的事情

Java Collections(二)

Map用于保存具有映射关系的数据,<key,value>。Map接口和Set接口很像,Set接口下的实现类在Map中都有对应的类,Set中的一个元素是Map中对应键值对的封装。

HashMap和Hashtable的关系类似于ArrayList和Vector的关系,Hashtable是线程安全的,而HashMap是线程不安全的,HashMap中可以保存null值(key和value都可以),而Hashtable中不能保存null。与HashSet类似,尽量不要使用可变对象作为HashMap和Hashtable的key,尽量不要修改元素的key值,会引起混乱。

LinkedHashMap用一个双向链表来维护key-value的次序,性能略低于HashMap,因为要维护一个链表。Properties类继承Hashtable,该对象在处理属性文件时特别方便,该类可以把Map对象和属性文件关联起来。

SortedMap接口的实现类是TreeMap类,TreeMap就是一个红黑树数据结构,每个<key,value>对就是一个红黑树的节点,根据key来进行排序。

WeakHashMap与HashMap的用法基本相似,区别在于,HashMap的key保留了对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应的键值对;但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的键值对。
[Java Code]

public void weak() {
        WeakHashMap whm = new WeakHashMap();
        whm.put(new String("a"), new String("a"));
        whm.put(new String("b"), new String("b"));
        whm.put(new String("c"), new String("c"));
        whm.put("java", new String("love"));
        System.out.println(whm);
        System.gc();//garbage collection
        System.runFinalization();//run finalize method
        System.out.println(whm);
    }

第二个输出只输出“java”键值对,其他abc被释放掉,因为是匿名对象,属于弱引用,“java” key被保存到了常量池。

IdentityHashMap类:该类实现机制和HashMap基本相似,但它在处理两个key相等时比较独特,在IdentityHashMap中,当且仅当两个key严格相等(内存地址也要一样)时,IdentityHashMap才认为两个key相等(对于HashMap,只要equals和hashCode一样就行)。

EnumMap类是一个与枚举一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值,在内部以数组形式保存,不允许key为null,但允许value为null。

Enumeration接口是Iterator迭代器的老版本,是线程安全的。

Java Collections(一)

Java集合类是一个特别有用的工具类,可以用于存储数量不等的多个对象,并可实现常用的数据结构。
Java集合大致可以分为:Set、List、Map三种体系:

  • Set:代表无序、不可重复的集合
  • List:代表有序、可重合的集合
  • Map:代表具有映射关系的集合

Java集合类主要由两个接口派生而来:Collection和Map,Collection和Map是java集合框架的根接口。Collection接口的继承树如下图所示:
Collection Map接口继承树如下图所示:
Map 使用Iterator接口可以遍历集合元素,Iterator本身并不提供盛装对象的能力。当使用Iterator对集合里的元素进行迭代时,Iterator并不是把集合本身传递给迭代变量,而是把集合元素的值传给迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响,但可以删除。
[Java Code]

public void iterator(){
        Collection books=new ArrayList();
        books.add("a");
        books.add("b");
        books.add("c");
        Iterator it=books.iterator();
        while(it.hasNext()){
            String book=(String)it.next();
            book="d";
        }
        System.out.println(books.toString());
    }

输出a,b,c

Iterator是fail-fast机制,一旦在迭代过程中检测到该集合已经被修改过,程序立即引发ConcurrentModificationException异常。Iterator方法的工作原理如下图所示: Iterator

Set集合:判断两个对象相同不是用==运算符,而是用equals方法。因此可以修改equals方法来添加相同的元素。

HashSet类:不是同步的,允许null值,HashSet底层实际上是一个HashMap,把<key,value>值封装成一个Map.Entry类,通过Entry类中的key来比较添加的值。HashSet判断两个对象相等的标准是通过equals方法比较相等,并且hashCode方法返回一样的值。当hashCode相同,当equals不同,元素会以链式存储,影响性能。如果修改一个HashSet中的值可能会出现混乱,因为hashCode可能会根据对象的值计算,当改掉该值时,该对象依然存储在原来的hashCode上,但是再次访问它计算hashCode就不是原来的位置了。

LinkedHashSet类:维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。该类底层实现是LinkedHashMap类,维护了一个双重链接列表。此链接列表定义了迭代顺序,该类不是同步的。

TreeSet类:是SortedSet接口的实现类,可以确保集合元素处于排序状态。TreeSet是通过比较元素大小来排序,而不是插入顺序,所以要求元素直接必须要是可比较的,如果把一个对象添加到TreeSet中,则该对象的类必须实现Comparable接口,否则程序会抛出异常。TreeSet使用两种排序方法:自然排序和定制排序。

  • 自然排序:调用元素的compareTo方法,按元素的升序排序。编程时要尽量确保equals方法和compareTo方法保持一致性。如果TreeSet中包含了可变对象,当可变对象的Field被修改时,TreeSet在处理这些对象时将会非常复杂,而且容易出错。
  • 定制排序:通过Comparator接口来重写compareTo方法。

TreeSet类是线程非安全的,源码中是用TreeMap来实现的,而TreeMap采用红黑树的结构来存储数据,是一种自平衡的排序二叉树,TreeSet把<key,value>值封装成Entry类来表示红黑树的一个节点。

EnumSet类:EnumSet类是一个专门为枚举类设计的集合类。EnumSet的集合元素必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显示或隐式指定。不允许加入null值。

可以通过Collection工具类的synchronizedSortedSet方法来包装Set集合的同步性。

SortedSet s=Collections.synchronizedSortedSet(new TreeSet(…));

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List接口还提供了listIterator方法,该方法返回一个ListIterator对象,ListIterator接口继承Iterator接口,还专门提供了一个前向迭代的功能,而且还可以通过add方法向List集合中添加元素(Iterator只能删除元素)。

ArrayList和Vector都是基于数组实现的List,底层的数据结构是数组,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。两个类都通过initialCapacity参数来设置该数组的长度,当向其中添加元素超出该数组长度时,它们的initialCapacity会自动增加。ArrayList是线程不安全的,而Vector是线程安全的。所以Vector的性能要比ArrayList的性能低。Vector还有个子类Stack,也是线程安全的,模仿了栈的操作

Queue用于模拟队列这种数据结构,FIFO。Deque代表一个双端队列,可以同时从两端来添加、删除元素,所以可以当成栈来使用。PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按照队列元素大小进行重新排序。

Deque接口提供了一个实现类ArrayDeque,是基于数组实现的双端队列,底层也是一个Object[]数组来保存元素,如果超出容量,则会重新分配一个Object[]数组来存储集合元素。因为Stack是线程安全的,所以性能较ArrayDeque和LinkedList较差,所以栈结构推荐使用ArrayDeque和LinkedList。

LinkedList类实现了Deque接口,所以可以当成栈,它的底层实现方式是链表,因此随机访问集合元素时性能较差,但在插入和删除方面性能非常出色。对于所有内部基于数组实现的集合,使用随机访问的性能比使用Iterator迭代访问的性能要好,因为随机访问数组会被映射成对数组元素的访问。

Largest Rectangle in Histogram

Problem:
题目

Solve:
主要思想:用一个栈来保存高度的索引,当当前索引的高度比前一个大的时候就加入栈中,当比它小时,就从栈中不断的弹出索引,计算最大矩形的值,直到当前索引的高度比栈顶索引高度大时停止,并把当前索引加入到栈中。

[Java Code]

public int largestRectangleArea(int[] height) {
     Stack<Integer> stack = new Stack<Integer>();
        int n = height.length, area = 0;
        for (int i = 0; i < n; i++) {
            while (!stack.empty() && height[stack.peek()] > height[i]) {
                int h = height[stack.peek()];
                stack.pop();
                int l = stack.empty() ? -1 : stack.peek();
                area = Math.max(area, h * (i - l - 1));
            }
            stack.push(i);
        }
        while (!stack.empty() && height[stack.peek()] > 0) {
            int h = height[stack.peek()];
            stack.pop();
            int l = stack.empty() ? -1 : stack.peek();
            area = Math.max(area, h * (height.length - 1 - l));
        }
        return area;   
}