面向对象六大原则以及设计模式

面向对象六大原则

1.单一职责原则

    所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就是具有多于一个的职责。单一职责原则是指一个类或者模块应该有且只有一个改变他的原因。通俗的说就是一个类只负责一项职责。

2.开闭原则

    对于扩展是开放的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,而对于其修改是关闭的,不要改动模块的源代码。通俗的说就是尽量通过拓展的方式实现系统的升级维护和新功能的添加,而不是修改已有的源代码。

3.里氏替换原则

    使用抽象和多态将设计中的静态结构改为动态结构,维持设计的封闭性。任何基类可以出现的地方,子类一定可以出现。

    在软件中,将一个基类对象替换成他的子类对象,程序将不会产生任何错误和异常。反过来则不成立。在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

4.依赖倒置原则    

    高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。程序要依赖于抽象接口,不要依赖于具体实现。简单来说就是要求对抽象进行编程,不要对实现进行编程。这样就降低了客户与实现模块间的耦合。(各个模块之间相互传递的参数声明为抽象类型,而不是声明为具体的实现类)

5.接口隔离原则

    一个类对另一个类的依赖应该建立在最小的接口上。其原则是将非常庞大,臃肿的接口拆分成更小更具体的接口。

6.迪米特原则

    又叫做最少知识原则,一个对象对其他对象应该有尽可能少的了解。通俗的讲,一个类应该对自己需要耦合或者调用的类知道的最少,不关心被耦合或调用类的内部的实现,只负责调用你提供的方法。


设计模式

1.单例设计模式

作用:保证在java应用程序中,一个类class只有一个实例存在。

好处:由于单例模式在内存中只有一个实例,减少了内存的开销。优化和共享资源访问。

单例模式可以避免对资源的多重占用,例如写一个文件时,由于只有一个实例存在于内存中,避免对同一个资源文件的同时写操作。

使用情况:建立目录,数据库操作等单线程操作。某个需要被频繁访问的实例对象。


基本的实现思路

单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

单例的实现主要是通过以下两个步骤:

将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;

在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。


使用方法:

1.饿汉式

public class Single{

    private static Single mInstance = new Single();

    private Single(){}

    public static Single getInstance(){

        return mInstance;

    }

}

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

2.饿汉式静态代码块

public class Single{

    private static Single mInstance ;

    static{mInstance=new Single();}

    private Single(){}

    public Single getInstance(){

           return mInstance;

}

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

3.懒汉式  线程不安全(不可用):

public class Single{

    private static Single mInstance;

    private Single(){}

    public static Single getInstance(){

        if(mInstance == null){

                   mInstance = new Single();

                        }

        return mInstance;

    }

}

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

4.懒汉式:(线程安全,同步方法)[不推荐用]

public class Single{

    private static Single mInstance;

    private Single(){}

    public static synchronized  Single getInstance(){

        if(mInstance == null){

                    mInstance = new Single();

                }

        return mInstance;

    }

}

解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。

缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

5.懒汉式:(线程安全,同步代码块)[不可用]

public class Single{

    private static Single mInstance = null;

    private Single(){}

    public static Single getInstance(){

        if(mInstance == null){

            synchronized(Single.class){

                    mInstance = new Single();

            }

        }

        return mInstance;

    }

}

由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

6.懒汉式:双重检查[推荐用]

public class Single{

    private static Single mInstance = null;

    private Single(){}

    public static Single getInstance(){

        if(mInstance == null){

            synchronized(Single.class){

                if(mInstance ==null){

                    mInstance = new Single();

                }

            }

        }

        return mInstance;

    }

}

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

优点:线程安全;延迟加载;效率较高。

7.静态内部类[推荐用]

public class Single{

    private Single(){}

    private static class SingleInstance{

        private static final Single MINSTANCE = new Single();

    }

    public static Single getInstance(){

        return SingleInstance.MINSTANCE;

    }

}

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

8.枚举[推荐用]

public enum Single{

        INSTANCE;

        public void getInstance(){

        }

}

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。


2.工厂模式

定义:定义一个用户创建对象的接口,让子类决定实例化哪个对象。工厂方法使一个类的实例化延迟到其子类。对同一个接口的实现类进行管理和实例化创建。

ex:

抽象产品类:

public abstract class Product{

    public abstract void method();//产品的抽象类方法,由具体的产品类去实现。

}

具体产品类A

public class ProductA extend Product{

    @override

    public void method(){

        Log.d("zrl","我是产品A")

    }

}

具体产品类B

public class ProductB extends Product{

    @override

    public void method(){

        Log.d("zrl","我是产品B")

    }

}

抽象工厂类:

public abstract class Factory{

    public abstract Product createProduct();

}

具体工厂类:

public class FactoryA extends Factory{

    @override

    public Procuct createProduct(){

        return new ProductA();

        //return new ProductB();   

    }

}

客户类:

public class Client{

    public static void main(String[] args){

        Factory factoryA =new FactoryA();

        Factory factoryB =new FactoryB();

        Product productA= factoryA.createProduct();

        Product productB = factoryB.createProduct();

        productA.method();

        productB.method();

        FactoryA factoryA1 =new FactoryA();

        Product product = factoryA1.createProduct();

        product.method();


    }

}

打印结果:我是产品A 

                 我是产品B 

                 我是产品A

反射的方式也可以表现工厂设计模式:

抽象工厂类:

public abstract class Factory{

    public abstract T createProduct(Class cls);

}

具体工厂类:(通过反射获取)

public class FactoryA extends Factory{

    @override

    public T createProduct(Class cls){

        Product p = null;

        try{

            p = (Product)Class.forName(cls.getName).newInstance();

        }catch(Exception e){

                ...

        }
        return (T) p ;

    }

}

client的实现

Factory factory = new FactoryA();

Product b = factory.createProduct(ProductB.class);

b.method();

3.适配器模式

我们经常碰到要将两个没有关系的类组合在一起使用,第一解决方案是:修改各自类的接口,但是如果我们没有源代码,或者,我们不愿意为了一个应用而修改各自的接口。 怎么办?

使用Adapter,在这两种接口之间创建一个混合接口。

定义:把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法再一起工作的两个类能够在一起工作。

使用场景:

1)系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容。

2)想要建立一个可以重复的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作

3)需要一个统一的输出接口,而输入端的类型不可预知。

模式中的角色:

需要适配的类(Adaptee):需要适配的类。

适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。

目标接口(Target):客户所期待的接口。可以是具体的或抽象的类,也可以是接口。

public interface Target{

    void targetMethod();

}

public class Adaptee {

    public void getRequest(){

        Log.d("zrl",“适配类的方法”);

    }

}

实现:

public class Adapter implements Target{

    Adaptee adaptee;

    public Adapter(Adaptee ad){

        adaptee = ad;

    }

    public void targetMethod(){

        adaptee.getRequest();

    }

}

测试类:

public class Client{

        public static void main(String[] args) {

        // 需要先创建一个被适配类的对象作为参数

        Target target=new Adapter(new Adaptee());

        target.targetMethod();

    }

}

如果Target和 Adaptee都是接口,并且都有实现类。 可以通过Adapter实现两个接口来完成适配。 

优点:

系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。   将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码,更好的扩展性

缺点:

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现。如果不是必要,不要使用适配器,而是直接对系统进行重构。

4.责任链模式

    使多个对象都有机会处理请求,从而避免请求的接受者和发送者之间的耦合关系,将这些对象连接成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。发送这个请求的客户端并不知道链上哪个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态的重新组织和分配责任。

编程中小提现:

if(a<10){

    ...

}

else if (a<20){

    ...

}

else if(a<30){

    ...

}

else{

    ...

}

优点:可以降低系统的耦合度(请求者与处理者代码分离),简化对象的相互连接,同时增强给对象指派责任的灵活性,增加新的请求处理类也很方便。

缺点:不能保证请求一定被接收,且对于比较长的责任链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不方便,每次都是从链头开始。

5.观察者模式

定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所依赖于它的对象都会得到通知并被自动更新。

你可能感兴趣的:(面向对象六大原则以及设计模式)