精华内容
下载资源
问答
  • 面向对象6大原则

    千次阅读 2019-05-18 22:40:48
    1.单一原则。一个类应该有且只有一个变化的原因。单一职责原则将不同的职责分离到单独的类,每一个职责都是一个变化的中心。需求变化时,将通过更改职责相关的类来体现。如果一个类拥有多于一个的职责,则多个职责...

    1.单一原则。一个类应该有且只有一个变化的原因。单一职责原则将不同的职责分离到单独的类,每一个职责都是一个变化的中心。需求变化时,将通过更改职责相关的类来体现。如果一个类拥有多于一个的职责,则多个职责耦合在一起,会有多于一个原因来导致这个类发生变化。一个职责的变化可能会影响到其他的职责,另外,把多个职责耦合在一起,影响复用性。

    2.里氏替换原则,就是要求继承是严格的is-a关系。所有引用基类的地方必须能透明地使用其子类的对象。在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

    3.依赖倒置原则。依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。低层模块尽量都要有抽象类或接口,或者两者都有。变量的声明类型尽量是抽象类或接口。

    4.接口分离原则。一个类对另一个类的依赖应该建立在最小的接口上,通俗的讲就是需要什么就提供什么,不需要的就不要提供。接口中的方法应该尽量少,不要使接口过于臃肿,不要有很多不相关的逻辑方法。

    5.多用组合(has-a),少用继承(is-a)。如果新对象的某些功能在别的已经创建好的对象里面已经实现,那么应当尽量使用别的对象提供的功能,使之成为新对象的一部分,而不要再重新创建。可以降低类与类之间的耦合程度。

    6.开闭原则。对修改关闭,对扩展开放。在软件的生命周期内,因为变化,升级和维护等原因需要对软件原有代码进行修改,可能会给旧代码引入错误,也有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现。不过这要求,我们要对需求的变更有前瞻性和预见性。其实只要遵循前面5中设计模式,设计出来的软件就是符合开闭原则的。

    转载于:https://blog.51cto.com/zhangzhao/2396810

    展开全文
  • 面向对象设计六大原则

    万次阅读 2019-04-09 23:23:45
    6大原则如下: 1)单一职责原则,一个合理的类,应该仅有一个引起它变化的原因,即单一职责,就是设计的这个类功能应该只有一个;  优点:消除耦合,减小因需求变化引起代码僵化。 2) 开-闭原则,讲的是设计要对扩展...

    6大原则如下:

    1)单一职责原则,一个合理的类,应该仅有一个引起它变化的原因,即单一职责,就是设计的这个类功能应该只有一个;

      优点:消除耦合,减小因需求变化引起代码僵化。

    2) 开-闭原则,讲的是设计要对扩展有好的支持,而对修改要严格限制。即对扩展开放,对修改封闭。

      优点:降低了程序各部分之间的耦合性,其适应性、灵活性、稳定性都比较好。当已有软件系统需要增加新的功能时,不需要对作为系统基础的抽象层进行修改,只需要在原有基础上附加新的模块就能实现所需要添加的功能。增加的新模块对原有的模块完全没有影响或影响很小,这样就无须为原有模块进行重新测试。

    3) 里氏代换原则,很严格的原则,规则是“子类必须能够替换基类,否则不应当设计为其子类。”也就是说,一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。

      优点:可以很容易的实现同一父类下各个子类的互换,而客户端可以毫不察觉。

    4) 依赖倒换原则,“设计要依赖于抽象而不是具体化”。换句话说就是设计的时候我们要用抽象来思考,而不是一上来就开始划分我需要哪些哪些类,因为这些是具体。

    “High level modules should not depend upon low level modules, both should depend upon abstractions. Abstractions should not depend upon details, details should depend upon abstractions.”

    高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

    另一种表述为: 要针对接口编程,不要针对实现编程。即“Program to an interface, not an implementation.”

      优点:人的思维本身实际上就是很抽象的,我们分析问题的时候不是一下子就考虑到细节,而是很抽象的将整个问题都构思出来,所以面向抽象设计是符合人的思维的。另外这个原则会很好的支持(开闭原则)OCP,面向抽象的设计使我们能够不必太多依赖于实现,这样扩展就成为了可能,这个原则也是另一篇文章《Design by Contract》的基石。

    5) 接口隔离原则,“将大的接口打散成多个小接口”,让系统解耦,从而容易重构,更改和重新部署。

      优点:会使一个软件系统功能扩展时,修改的压力不会传到别的对象那里。

    6) 迪米特法则或最少知识原则,这个原则首次在Demeter系统中得到正式运用,所以定义为迪米特法则。它讲的是“一个对象应当尽可能少的去了解其他对象”。

      优点:消除耦合。

    展开全文
  • 面向对象的六大原则

    万次阅读 多人点赞 2019-02-13 18:36:34
    一、面向对象的六大原则  现在的编程的主流语言基本上都是面向对象的。我们知道,面向对象是一种编程思想,包括三特性和六大原则,其中,三特性指的是封装、继承和多态;六大原则指的是单一职责原则、开闭式...

    一、面向对象的六大原则

           现在的编程的主流语言基本上都是面向对象的。我们知道,面向对象是一种编程思想,包括三大特性和六大原则,其中,三大特性指的是封装、继承多态;六大原则指的是单一职责原则、开闭式原则、迪米特原则、里氏替换原则、依赖倒置原则以及接口隔离原则其中,单一职责原则是指一个类应该是一组相关性很高的函数和数据的封装,这是为了提高程序的内聚性,而其他五个原则是通过抽象来实现的,目的是为了降低程序的耦合性以及提高可扩展性。在应用的开发过程中,最难的不是完成应用的开发工作,而是在后续的升级、维护过程中让系统能够拥抱变化。拥抱变化也意味着在满足需求且不破坏系统稳定性的前提下保持高可扩展性、高内聚、低耦合,在经历了各个版本的变更之后依然能够保持清晰、灵活、稳定的系统架构。当然,这是一种比较理想的情况,由于各种各样的原因(开发水平差、工期短、产品奇葩需求等),我们的应用可能会变得难以维护,但是我们必须向更好的方向努力,那么遵循面向对象的六大原则就是我们走向灵活软件之路所迈出的第一步。本篇文章将以实现一个简单的图片加载框架为例,来说明面向对象六大原则的应用。

    二、图片加载框架需求分析

    需求描述

    1、需要根据Url将图片加载到对应的ImageView上;

    2、需要图片缓存功能;

    3、框架提供的API尽可能简单,方便使用;

    4、框架能够灵活的扩展,比如灵活的改变缓存功能、下载图片方法等;

    需求分析

    分析一下需求,我们至少需要使用以下技术:

    1、首先,我们需要根据Url下载图片,这里暂定使用UrlConnection;

    2、为了不阻塞UI线程,我们下载图片需要在子线程中执行,方便起见,我们直接使用线程池;

    3、在子线程中下载图片后,我们需要将图片显示到ImageView上,由于Android的特性,我们需要在UI线程中更新UI,所以这里需要使用Handler来切换线程到UI线程中显示图片;

    4、图片需要缓存功能,一般图片缓存需要有内存缓存和文件缓存,内存缓存就是将下载好的图片保存在内存中,下次使用时直接从内存中获取,这里采用Lru算法来控制图片缓存,文件缓存即将图片缓存到文件中,下次使用直接从文件中获取,相对来说,内存缓存会占用较多的内存,但是效率较高。

    三、源码实现

    我们首先只实现功能,而不管代码的好坏,后面我们再根据六大原则对代码进行优化,看看遵循六大原则究竟能够带来哪些好处。

    public class ImageLoader {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        private LruCache<String, Bitmap> mLruCache = new LruCache<>(maxMemory / 4); // 内存缓存
        ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // 下载图片使用的线程池
        private Handler mDealHandler = new Handler() { // 处理下载好图片的Handler
            @Override
            public void handleMessage(Message msg) {
                if (msg != null && msg.obj != null) {
                    ImageResponse imageResponse = (ImageResponse) msg.obj;
                    imageResponse.imageView.setImageBitmap(imageResponse.bitmap); // 将图片显示到ImageView上
                    mLruCache.put(imageResponse.imageUrl, imageResponse.bitmap); // 将图片保存到缓存中
                }
            }
        };
    
        /**
         * 加载图片
         * @param imageView
         * @param imageUrl
         */
        public void displayImage(final ImageView imageView, final String imageUrl) {
            if (imageView == null || TextUtils.isEmpty(imageUrl)) {
                return;
            }
    
            Bitmap cacheBitmap = mLruCache.get(imageUrl);
            if (cacheBitmap == null) { // 使用缓存失败
                mExecutorService.submit(new Runnable() { // 使用线程池下载图片
                    @Override
                    public void run() {
                        Bitmap bitmap = downLoadImage(imageUrl); // 下载图片
                        if (bitmap != null) {
                            ImageResponse imageResponse = new ImageResponse(); // 图片下载好后,将信息封装成Message发送给Handler处理
                            imageResponse.bitmap = bitmap;
                            imageResponse.imageUrl = imageUrl;
                            imageResponse.imageView = imageView;
                            Message message = Message.obtain();
                            message.obj = imageResponse;
                            mDealHandler.sendMessage(message);
                        }
                    }
                });
            } else {
                imageView.setImageBitmap(cacheBitmap);
            }
        }
    
        // 下载图片
        public Bitmap downLoadImage(final String imageUrl) {
            Bitmap bitmap = null;
            try {
                URL url = new URL(imageUrl);
                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                bitmap = BitmapFactory.decodeStream(connection.getInputStream());
                connection.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return bitmap;
        }
        
        public static class ImageResponse {
            public Bitmap bitmap;
            public ImageView imageView;
            public String imageUrl;
        }
    }

    使用方法:

        ImageLoader mImageLoader = new ImageLoader();
        mImageLoader.displayImage(imageView, imageUrl);

    在上面的代码中,我们不管三七二十一,将所有的功能都堆积到了ImageLoader这个类中,ImageLoader不仅承担了提供Api接口的功能,同时还承担了具体实现细节的功能,比如缓存功能、下载图片功能、显示图片功能等等,这样虽然也能实现图片加载的效果,但是确会让我们的代码变得臃肿复杂并且难以维护,不说别人看不懂,可能过个几天后连自己都看不懂了,所以,我们自然而然的要想到,我们需要根据功能对类进行细分,将某些相关性很强的功能分到独立的类中去处理,比如,图片的缓存我们可以划分为一个类,图片的下载可以作为另外一个类,通过类的划分,我们可以让代码变得清晰,这也就是面向对象六大原则中的第一个原则----单一职责原则

    优化代码第一步----单一职责原则----高内聚

    单一职责原则的定义是:就一个类而言,应该只有一个引起它变化的原因。简单来说,一个类应该是一组相关性很高的函数和数据的封装。因为单一职责的划分界限并不是那么清晰,每个人的理解不一样,这就导致了不同的人划分的类承担的职责也不一样,就图片加载的例子来说,可能有的人就认为整个图片加载是一组相关性很高的功能,于是将其放入在一个类中处理。一般来说,我们首先需要具备单一职责原则的思想,如果发现一个类承担了太多的功能,这个时候就要考虑将某些功能划分到其他类中去处理,具体的划分细节要平开发者的个人经验。

    下面,我们利用单一职责原则的思想对ImageLoader进行改造,根据功能来说,下载图片和图片缓存应该属于单独的功能,我们可以分别用两个类来实现,然后ImageLoader调用这两个类即可:

    public class ImageLoader {
        ImageCache mImageCache = new ImageCache();
        RequestImage mRequestImage = new RequestImage();
        
        private Handler mDealHandler = new Handler() { // 处理下载好图片的Handler
            @Override
            public void handleMessage(Message msg) {
                if (msg != null && msg.obj != null) {
                    ImageResponse imageResponse = (ImageResponse) msg.obj;
                    imageResponse.imageView.setImageBitmap(imageResponse.bitmap); // 将图片显示到ImageView上
                    mImageCache.put(imageResponse.imageUrl, imageResponse.bitmap); // 将图片保存到缓存中
                }
            }
        };
    
        /**
         * 加载图片
         * @param imageView
         * @param imageUrl
         */
        public void displayImage(final ImageView imageView, final String imageUrl) {
            if (imageView == null || TextUtils.isEmpty(imageUrl)) {
                return;
            }
    
            Bitmap cacheBitmap = mImageCache.get(imageUrl);
            if (cacheBitmap == null) { // 使用缓存失败
                mRequestImage.requestImage(imageView, imageUrl, mDealHandler);
            } else {
                imageView.setImageBitmap(cacheBitmap);
            }
        }
        
        public static class ImageResponse {
            public Bitmap bitmap;
            public ImageView imageView;
            public String imageUrl;
        }
    }
    
    public class RequestImage {
        ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // 下载图片使用的线程池
    
        public void requestImage(final ImageView imageView, final String imageUrl, final Handler handler) {
            mExecutorService.submit(new Runnable() { // 使用线程池下载图片
                @Override
                public void run() {
                    Bitmap bitmap = downLoadImage(imageUrl); // 下载图片
                    if (bitmap != null) {
                        ImageLoader.ImageResponse imageResponse = new ImageLoader.ImageResponse(); // 图片下载好后,将信息封装成Message发送给Handler处理
                        imageResponse.bitmap = bitmap;
                        imageResponse.imageUrl = imageUrl;
                        imageResponse.imageView = imageView;
                        Message message = Message.obtain();
                        message.obj = imageResponse;
                        handler.sendMessage(message);
                    }
                }
            });
        }
    
        // 下载图片
        public Bitmap downLoadImage(final String imageUrl) {
            Bitmap bitmap = null;
            try {
                URL url = new URL(imageUrl);
                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                bitmap = BitmapFactory.decodeStream(connection.getInputStream());
                connection.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return bitmap;
        }
    }
    
    public class ImageCache {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        private LruCache<String, Bitmap> mLruCache = new LruCache<>(maxMemory / 4); // 内存缓存
    
        public void put(String url, Bitmap bitmap) {
            if (!TextUtils.isEmpty(url) && bitmap != null) {
                mLruCache.put(url, bitmap);
            }
        }
    
        public Bitmap get(String url) {
            return mLruCache.get(url);
        }
    }

    可以看到,经过单一职责原则的优化,我们的ImageLoader变得简洁了许多,也更加易于维护,比如,我们想修改缓存相关功能,只需要查看ImageCache类即可。我们并没有对具体功能代码做修改,只是将相关性较高的功能单独提取了出来,封装成类。单一职责原则也就是我们经常说的高内聚。

    更高的可扩展性----开闭式原则

    经过单一职责原则的优化,我们的ImageLoader变得不再臃肿,但是,还远远没有达到要求,我们在需求中说过,ImageLoader需要支持内存缓存和文件缓存,现在我们只支持了内存缓存,可能你会说,在原来的基础上加上文件缓存不就可以了吗?我们先来看看,直接在ImageLoader中加入文件缓存后的代码会是什么样的:

        MemoryImageCache mMemoryImageCache = new MemoryImageCache();
        DiskImageCache mDiskImageCache = new DiskImageCache(App.getAppContext());
        private int mCacheType = 1;
        public final static int MEMORY_CACHE_TYPE = 1;
        public final static int DISK_CACHE_TYPE = 2;
    
        /**
         * 设置缓存类型
         * @param type
         */
        public void setCacheType(int type) {
            mCacheType = type;
        }
    

    首先,我们添加了一个变量mCacheType来保存缓存类型,并提供了一个setCacheType方法来设置缓存类型,然后在使用缓存时,我们需要根据保存的缓存类型来确定到底该使用哪个缓存:

        switch (mCacheType) {
    	        case MEMORY_CACHE_TYPE:
    	            mMemoryImageCache.put(imageResponse.imageUrl, imageResponse.bitmap); // 将图片保存到内存缓存中
    	            break;
    	        case DISK_CACHE_TYPE:
    	            mDiskImageCache.put(imageResponse.imageUrl, imageResponse.bitmap); // 将图片保存在磁盘缓存中
    	            break;
    	    }
    
        Bitmap cacheBitmap = null;
        switch (mCacheType) {
            case MEMORY_CACHE_TYPE:
                cacheBitmap = mMemoryImageCache.get(imageUrl); // 从内存缓存中获取图片
                break;
            case DISK_CACHE_TYPE:
                cacheBitmap = mDiskImageCache.get(imageUrl); // 从磁盘缓存中获取图片
                break;
        }

    可以看到,无论是获取缓存还是保存缓存,都需要进行判断,不仅麻烦还容易出错,试想一下,如果这时我们需要ImageLoader支持双缓存,即优先使用内存缓存,当内存缓存中没有时,我们需要使用文件缓存(在实际应用中,图片加载框架一般都是双缓存的,这样既能保证加载速度,又能尽可能的减少图片的下载),那么应该怎么办呢?难道还是和添加文件缓存一样,在ImageLoader中再加入一种缓存内型?显示,这种方式是不合理的,不仅让代码变得难以维护,而且扩展性极差,这个时候,开闭式原则就派上用场了。

    开闭式原则的定义:软件中的对象应该对扩展是开放的,但是,对修改是关闭的。软件开发过程中,需求是不断变化的,因为变化、升级和维护等原因需要对原有的软件代码进行修改,而一旦对原有的代码进行修改,就有可能影响到原有的模块,引起bug,因此,在软件开发过程中,我们应该尽可能通过扩展的方式实现变化,而不是修改原有的代码,当然,这是一种理想的情况,在实际的软件开发中,完全通过扩展的方式实现变化是不现实的。那么,如何让对象对扩展是开放的呢?回顾一下面向对象的三大特性:封装、继承和多态,在单一职责原则中,我们使用到了封装的特性,而继承和多态还没有使用,现在是派上用场的时候了,继承和多态的精髓在于抽象,一般来说,高层次模块不应该直接依赖低层次模块的实现细节,而应该依赖其抽象,在ImageLoader这个例子中,ImageLoader直接依赖了具体的缓存类,这就让ImageLoader和具体的缓存类紧紧的耦合在一起,我们一直强调软件开发要做到高内聚、低耦合,高内聚通过单一职责原则可以达到,而低耦合则需要依赖抽象。

    通过分析,我们不难发现无论是哪种缓存类,其都需要提供获取缓存和放入缓存的功能,即get和put,既然这样,我们为什么不将其抽象成接口呢?

    /**
     * 缓存图片接口类
     * 这个接口定义了缓存图片所需要的基本功能,缓存图片以及从缓存中获取图片
     */
    public interface ImageCache {
        public void put(String url, Bitmap bitmap); // 缓存图片
        public Bitmap get(String url); // 获取图片
    }
    

    然后具体的缓存类实现ImageCache接口:

    /**
     * ImageLoader内存缓存类,采用Lru算法
     */
    public class MemoryImageCache implements ImageCache {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        private LruCache<String, Bitmap> mLruCache = new LruCache<>(maxMemory / 4);
    
        @Override
        public void put(String url, Bitmap bitmap) {
            if (!TextUtils.isEmpty(url) && bitmap != null) {
                mLruCache.put(url, bitmap);
            }
        }
    
        @Override
        public Bitmap get(String url) {
            return mLruCache.get(url);
        }
    }
    
    /**
     * ImageLoader文件缓存
     */
    public class DiskImageCache implements ImageCache {
        private Context mContext;
    
        public DiskImageCache() {
            this.mContext = App.getContext();
        }
    
        @Override
        public void put(String url, Bitmap bitmap) {
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(DiskCacheFileUtils.getImageCacheFile(url, this.mContext));
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
                fileOutputStream.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                CloseUtils.close(fileOutputStream);
            }
        }
    
        @Override
        public Bitmap get(String url) {
            String imageCachePath = DiskCacheFileUtils.getImageCachePath(url, this.mContext);
            if (DiskCacheFileUtils.isFileExists(imageCachePath)) {
                return BitmapFactory.decodeFile(imageCachePath);
            }
            return null;
        }
    }
    
    /**
     * ImageLoader双缓存类
     */
    public class DoubleImageCache implements ImageCache {
        private ImageCache mMemoryImageCache;
        private ImageCache mDiskImageCache;
    
        public DoubleImageCache() {
            mMemoryImageCache = new MemoryImageCache();
            mDiskImageCache = new DiskImageCache();
        }
    
        @Override
        public void put(String url, Bitmap bitmap) {
            mMemoryImageCache.put(url, bitmap);
            mDiskImageCache.put(url, bitmap);
        }
    
        @Override
        public Bitmap get(String url) {
            Bitmap bitmap = mMemoryImageCache.get(url);
            if (bitmap != null) {
                return bitmap;
            }
            bitmap = mDiskImageCache.get(url);
            if (bitmap != null) {
                return bitmap;
            }
            return null;
        }
    }
    

    上面的三个具体的缓存类,都实现了ImageCache接口,表示其具备了缓存能力,我们再来看一下在ImageLoader中如何设置缓存:

        private ImageCache mImageCache;
    
        /**
         * 设置缓存
         * @param imageCache(默认使用MemoryImageCache缓存)
         * @return
         */
        public ImageLoader setImageCache(ImageCache imageCache) {
            this.mImageCache = imageCache;
            return this;
        }

    使用缓存:

            if (mImageCache != null) { // 将图片放入缓存
        	    mImageCache.put(url, bitmap);
            }
    
            Bitmap cacheBitmap = mImageCache.get(url); // 获取缓存

    可以看到,ImageLoader并没有依赖于具体的缓存实现类,而只是依赖了缓存类接口ImageCahe,通过这种方式,我们让ImageLoader具备了可以兼容任何实现了ImageCache接口的缓存类的能力,比如,现在ImageLoader想使用双缓存,只需要调用如下代码即可:

            imageLoader.setImageCache(new DoubleImageCache());

    再比如,我们想使用其他的缓存方案,只需要定义一个缓存类,实现ImageCache接口即可:

    imageLoader.setImageCache(new ImageCache() {
                @Override
                public void put(String url, Bitmap bitmap) {
                    // 具体的放入缓存实现
                }
    
                @Override
                public Bitmap get(String url) {
                    // 具体的获取缓存实现
                    return null;
                }
            });

    通过遵循开闭式原则,我们大大提高了缓存功能的可扩展性,并且去除了ImageLoader与具体缓存类的耦合,这一切都要归功于抽象。

    里氏替换原则

    通过前面的优化,我们的ImageLoader已经大体上满足需求了,我们再来看一下里氏替换原则的定义:所有引用基类的地方都必须能够透明的使用其子类,这句话是什么意思呢,以我们的ImageLoader为例,所有引用到ImageCache的地方应该都可以替换成具体的子类对象。想象一下,如果我们的ImageCache中的ImageCache不能够被具体的缓存类所替换,那我们应该如何给ImageCahce设置缓存呢?难道还是使用原来的mCacheType的方式吗?显然不是,里氏替换原则就是给这类问题提供了指导原则,通过建立抽象,让高层次模块依赖于抽象类,在运行时在替换成具体的实现类,保证了系统的扩展性和灵活性

    依赖倒置原则

    依赖倒置原则指的是高层次模块不应该依赖于低层次模块的具体实现,两者都应该依赖其抽象,具体如下:

    1、高层次模块不应该依赖低层次模块的具体实现,两者都应该依赖其抽象;

    2、抽象不应该依赖细节;

    3、细节应该依赖抽象。

    在面向对象语言中,抽象指的是接口或者抽象类,两者都不能被实例化,而细节指的是具体的实现类。其实一句话就可以概括,面向接口编程,或者说面向抽象编程,试想一下,如果类和类之间之间依赖细节,那么这两个类将会紧紧的耦合在一起,这就意味着,修改了其中一个类,很可能需要对另外一个类也需要进行修改,并且这样也大大限制了系统的可扩展性。依赖倒置原则提供了一种解耦方式,通过抽象让类和类之间不再依赖细节,而是在具体运行时再进行替换。以我们的ImageLoader为例,通过建立抽象类ImageCache,我们让ImageLoader不再依赖于具体的缓存类,并且能够灵活的扩展使用其他的缓存功能。从上面几个原则来看,想要让系统具备更好的可扩展性,抽象似乎成为了唯一的手段。

    接口隔离原则

    接口隔离原则的定义:类间的依赖关系应该建立在最小的接口之上。接口隔离原则将非常庞大、臃肿的接口拆分成更小更具体的接口。 一个接口定义的过于臃肿,则代表它的每一个实现类都要考虑所有的实现逻辑。如果一个类实现了某个接口,也就是说这个类承载了这个接口所有的功能,维护这些功能成为了自己的职责。这就无形中增加了一个类的负担。这里有一点需要说明,接口定义要小,但是要有限度,对接口细化可以增加灵活性,但是过度细化则会使设计复杂化。同时接口的使用率不高,提高了代码的维护成本。这种极端的体现就是每个接口只含有一个方法,这显然是不合适的。以前面的ImageCache为例,图片的缓存类必须提供两个功能,放入缓存和获取缓存,所以ImageCache封装了这两个抽象方法,而不应该再进行细分。我们再举一个接口隔离的例子,我们在文件缓存类中用到了FileOutputStream,在Java中,在使用了可关闭对象后,需要调用其close方法对其进行关闭:

        public void put(String url, Bitmap bitmap) {
            FileOutputStream fileOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(DiskCacheFileUtils.getImageCacheFile(url, this.mContext));
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
                fileOutputStream.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                 if (fileOutputStream != null) { // 关闭fileOutputStream对象
                    try {
                        fileOutputStream .close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                 }
            }
        }

    我们可以看到这段代码的可读性非常差,各种try...catch,并且,在Java中,类似FileOutputStream这种需要关闭的对象有很多,难道我们每次使用时都要加上这种判断吗?有没有什么办法可以优化呢?答案是肯定的,在Java中,这种需要关闭的对象都继承了Closeable接口,表示这个对象是可以关闭的,Closeable接口的定义如下:

    public interface Closeable extends AutoCloseable {
        public void close() throws IOException;
    }

    我们可以建立一个工具类来专门处理继承了Closeable接口的类对象的关闭处理:

    public class CloseUtils {
        public static void close(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    这样,我们就可以将关闭FileOutputStream的代码修改为:

        CloseUtils.close(fileOutputStream);

    并且这个工具类适用于关闭所用继承了Closeable接口的对象。

    通过前面的学习,我们发现这几大原则可以总结为下面几个关键点:单一职责、抽象、最小化。在实际开发中,我们要灵活的运行这几大原则。

    迪米特原则

    迪米特原则的定义:一个对象应该对其他对象有最小的了解。迪米特原则也称作最小知道原则,即类和类直接应该建立在最小的依赖之上,一个类应该对其依赖的类有最小的了解,即只需要知道其所需要的方法即可,至于其内部具体是如何实现的则不用关心。迪米特原则的目的是减少类和类之间的耦合性,类和类之间的耦合性越大,当一个类发生修改后,会其他类造成的影响也越大。以前面的ImageLoader为例,使用者在用ImageLoader加载图片时,应该只需要和ImageLoader打交道,而不用关心具体的图片时如何下载、缓存以及显示的,并且我们在封装ImageLoader类时,还应该只将一些必要的方法设置为public方法,这些public方法表示提供给使用者使用的方法,比如加载图片和设置缓存,而对于其他的方法应该设置为private,将其隐藏起来。迪米特原则总结来说就是通过最小了解来降低类之间的耦合。

    总结

    在这篇文章中,我们通过设计一个简易的图片加载框架来说明面向对象六大原则的应用,六大原则指的是单一职责原则、开闭式原则、迪米特原则、里氏替换原则、依赖倒置原则以及接口隔离原则其中,单一职责原则是指一个类应该是一组相关性很高的函数和数据的封装,这是为了提高程序的内聚性,而其他五个原则是通过抽象来实现的,目的是为了降低程序的耦合性以及提高可扩展性。六大原则的目的是为了让程序达到高内聚、低耦合,提高可扩展性的目的,其实现手段是面向对象的三大特性:封装、继承以及多态。

    展开全文
  • 面向对象设计的七大原则(包括SOLID原则原则概述1. 单一职责原则(Single Responsibility Principle)2. 开闭原则(Open Close Principle)3. 里氏替换原则(Liskov Substitution Principle)4. 接口隔离原则...

    原则概述

    1. 单一职责原则(Single Responsibility Principle)

    每一个类应该专注于做一件事情。
    

    2. 开闭原则(Open Close Principle)

    面向扩展开放,面向修改关闭。
    

    3. 里氏替换原则(Liskov Substitution Principle)

    超类存在的地方,子类是可以替换的。
    

    4. 接口隔离原则(Interface Segregation Principle)

    应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。
    

    5. 依赖倒置原则(Dependence Inversion Principle)

    实现尽量依赖抽象,不依赖具体实现。
    

    6. 迪米特法则(Law Of Demeter)

    又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。
    

    7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)

    尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。
    

    原则详解

    1. 单一职责原则(Single Responsibility Principle)

    因为:

    可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

    所以:

    从大局上看Android中的Paint和Canvas等类都遵守单一职责原则,Paint和Canvas各司其职。

    示例:

    新建一个Rectangle类,该类包含两个方法,一个用于把矩形绘制在屏幕上,一个方法用于计算矩形的面积。

    Rectangle类违反了SRP原则。Rectangle类具有两个职责,如果其中一个改变,会影响到两个应用程序的变化。

    一个好的设计是把两个职责分离出来放在两个不同的类中,这样任何一个变化都不会影响到其他的应用程序。

    2. 开闭原则(Open Close Principle)

    因为:

    开放封闭原则主要体现在对扩展开放、对修改封闭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。软件需求总是变化的,世界上没有一个软件的是不变的,因此对软件设计人员来说,必须在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。

    所以:

    可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。
    封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象。

    示例:

    public boolean sendByEmail(String addr, String title, String content) {
    }public boolean sendBySMS(String addr, String content) {
    }
    // 在其它地方调用上述方法发送信息
    sendByEmail(addr, title, content);
    sendBySMS(addr, content);
    

    如果现在又多了一种发送信息的方式,比如可以通过QQ发送信息,那么不仅需要增加一个方法sendByQQ(),还需要在调用它的地方进行修改,违反了OCP原则,更好的方式是

    抽象出一个Send接口,里面有个send()方法,然后让SendByEmail和SendBySMS去实现它既可。这样即使多了一个通过QQ发送的请求,那么只要再添加一个SendByQQ实现类实现Send接口既可。这样就不需要修改已有的接口定义和已实现类,很好的遵循了OCP原则。

    3. 里氏替换原则(Liskov Substitution Principle)

    因为:

    里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

    所以:

    使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。

    从大局看Java的多态就属于这个原则。

    4. 接口隔离原则(Interface Segregation Principle)

    不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

    客户模块不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性。如Java中一个类实现多个接口,不同的接口给不用的客户模块使用,而不是提供给客户模块一个大的接口。

    示例:

    public interface Animal {
    	public void eat(); // 吃
    	public void sleep(); // 睡
    	public void crawl(); // 爬
    	public void run(); // 跑
    }
    
    public class Snake implements Animal {
    	public void eat() {
    	}
    	public void sleep() {
    	}
    	public void crawl() {
    	}
    	public void run() {
    	}
    }
    
    public class Rabit implements Animal {
    	public void eat() {
    	}
    	public void sleep() {
    	}
    	public void crawl() {
    	}
    	public void run() {
    	}
    }
    

    上面的例子,Snake并没有run的行为而Rabbit并没有crawl的行为,而这里它们却必须实现这样不必要的方法,更好的方法是crawl()和run()单独作为一个接口,这需要根据实际情况进行调整,反正不要把什么功能都放在一个大的接口里,而这些功能并不是每个继承该接口的类都所必须的。

    5. 依赖倒置原则(Dependence Inversion Principle)

    因为:

    具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类;而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口;这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。

    所以:

    采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。

    从大局看Java的多态就属于这个原则。

    6. 迪米特法则(Law Of Demeter)

    因为:

    类与类之间的关系越密切,耦合度也就越来越大,只有尽量降低类与类之间的耦合才符合设计模式;对于被依赖的类来说,无论逻辑多复杂都要尽量封装在类的内部;每个对象都会与其他对象有耦合关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说,不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。

    所以:

    一个对象对另一个对象知道的越少越好,即一个软件实体应当尽可能少的与其他实体发生相互作用,在一个类里能少用多少其他类就少用多少,尤其是局部变量的依赖类,能省略尽量省略。同时如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一方法的话,可以通过第三者转发这个调用。

    从大局来说Android App开发中的多Fragment与依赖的Activity间交互通信遵守了这一法则。

    7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)

    因为:

    其实整个设计模式就是在讲如何类与类之间的组合/聚合。在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。

    如果为了复用,便使用继承的方式将两个不相干的类联系在一起,违反里氏代换原则,哪是生搬硬套,忽略了继承了缺点。继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类,基类的内部细节常常对派生类是透明的,白箱复用;虽然简单,但不安全,不能在程序的运行过程中随便改变;基类的实现发生了改变,派生类的实现也不得不改变;从基类继承而来的派生类是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。

    所以:

    组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

    参考文章:
    https://www.cnblogs.com/sunflower627/p/4718702.html
    https://www.imooc.com/article/51098#

    展开全文
  • 1.面向对象特性六大原则

    千次阅读 2018-04-04 12:48:17
    JAVA是一门面向对象的语言,那么其面向对象主要有以下几个特性和原则面向对象特性六大原则面向对象的三特性是"封装、"多态"、"继承",五大原则是"单一职责原则"、"...
  • 面向对象编程 - 六大原则

    千次阅读 多人点赞 2018-03-18 22:05:33
    这里简单介绍面向对象编程的几个原则,根据这些原则去设计类以及类与类之间的关系,从而使得程序满足高可维护、可复用等条件。面向对象编程与面向过程编程的理解:(纯属个人理解) 面向过程编程:好比做一件事,...
  • 面向对象的五基本原则

    千次阅读 2019-01-06 20:18:24
    面向对象的七(或五大原则,前五项)基本原则:单一职责原则(SRP) 、开放封闭原则(OCP) 、里氏替换原则(LSP)、 依赖倒置原则(DIP)、 接口隔离原则(ISP)、迪米特法则(Law Of Demeter)、组合/聚合复用原则...
  • 面向对象之七大原则

    千次阅读 2018-07-18 17:27:49
    单一职责,里氏替换,迪米特法则,依赖倒转,接口隔离,合成/聚合原则,开放-封闭 。 1. 开闭原则(Open-Closed Principle, OCP) 定义:软件实体应当对扩展开放,对修改关闭。这句话说得有点专业,更通俗一点讲,...
  • 面向对象编程七大原则

    千次阅读 2017-10-30 13:31:06
    1. 开闭原则(Open-Closed Principle, OCP) 定义:软件实体应当对扩展开放,对修改关闭。这句话说得有点专业,更通俗一点讲,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能...
  • 设计模式之面向对象基本原则

    万次阅读 2015-04-27 16:25:48
    概述在运用面向对象的思想进行软件设计时,需要遵循的原则一共有7个,他们是:1. 单一职责原则(Single Responsibility Principle)每一个类应该专注于做一件事情。2. 里氏替换原则(Liskov Substitution Principle...
  • 面向对象大原则

    万次阅读 多人点赞 2015-11-30 00:10:44
    1、优化代码的第一步——单一职责原则单一职责原则的英文名称是Single Responsibility Principle,简称SRP。它的定义是:就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的...
  • 面向对象设计六基本原则

    千次阅读 2020-12-22 23:20:34
    面向对象设计模式六基本原则 本篇开始介绍面向对象设计思想 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录面向对象设计模式六基本原则前言一、pandas是什么?二、使用步骤1.引...
  • 面向对象原则.zip

    2020-08-05 17:39:13
    1面向对象6大原则--单一职责原则 2面向对象6大原则--里氏代换原则 3面向对象6大原则--开闭原则 4面向对象6大原则--依赖倒转原则 5面向对象6大原则--合成复用原则 6面向对象6大原则--接口隔离原则
  • 面向对象设计七大原则

    千次阅读 2018-06-07 17:28:00
    然后对面向对象设计的一些原则进行了一些学习和整理。包括SOLID、合成复用原则与迪米特法则。可维护性Robert C.Martin认为⼀个可维护性较低的软件设计,通常由于如下四个原因造成:• 过于僵硬(Rigidity)• 过于脆弱...
  • 面向对象设计的七设计原则详解

    万次阅读 多人点赞 2018-10-03 12:32:21
    文章目录面向对象的七设计原则简述七大原则之间的关系一、开闭原则(The Open-Closed Principle ,OCP)概念理解系统设计需要遵循开闭原则的原因开闭原则的实现方法一个符合开闭原则的设计开闭原则的相对性二、 ...
  • 面向对象基本原则详解

    千次阅读 2018-12-06 17:27:36
    面向对象基本原则单一职责原则计算器实例加法类AddJiSuanQi减法类SubJiSuanQi开放封闭原则(OCP)开放封闭原则示例(书店售书)类图代码实现第一个办法:第二个办法:第三个办法:代码实现归纳变化:扩展接口再...
  • 一、面向对象编程的6大设计原则单一职责原则——类要职责单一,一个类只需要做好一件事情。里氏替换原则——子类可以扩展父类的功能,但不能改变父类原有的功能(可以实现父类的抽象方法和增加自己特有的方法,不要...
  • 面向对象设计的八基本原则

    千次阅读 2018-10-21 22:10:03
    一、八基本原则 1. 单一职责原则(Single Responsibility Principle) 每一个类应该专注于做一件事情。 2. 里氏替换原则(Liskov Substitution Principle) 超类存在的地方,子类是可以替换的。 3. 依赖倒置...
  • 6.恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计...
  • Java面向对象

    千次阅读 多人点赞 2019-05-08 23:33:50
    面向对象特性五大原则1.三特性:2.五大原则:总结: 1.Java面向对象是什么 OOP(面向对象编程)、OOD(面向对象设计)、OOA(面向对象的分析) Object Oriented Programming     &nb...
  • 浅谈面向对象大原则 S.O.L.I.D

    千次阅读 2018-05-18 17:59:38
    Single Responsibility Principle(SRP) 单一职责原则 A class should have one and only one reason to change, meaning that a class should have only one job. 定义 什么是职责 在SRP,我们把职责定义为...
  • 面向对象6大原则

    千次阅读 2017-01-11 17:37:55
    软件的设计开发应遵循以下六大原则: 1. OCP 全称:“Open-Closed Principle” 开放-封闭原则 说明:对扩展开放,对修改关闭。 优点:按照OCP原则设计出来的系统,降低了程序各部分之间的耦合性,其适应性、...
  • C#面向对象设计的七大原则

    千次阅读 2018-02-04 12:19:05
    本文我们要谈的七大原则,即:单一职责,里氏替换,迪米特法则,依赖倒转,接口隔离,合成/聚合原则,开放-封闭 。 1. 开闭原则(Open-Closed Principle, OCP) 定义:软件实体应当对扩展开放,对修改关闭。这句...
  • 面向对象程序设计六大原则

    千次阅读 2017-07-11 17:16:23
    面向对象程序设计中,需要遵守的原则可总结为6个,这就是大名鼎鼎的六大原则面向对象程序设计原则也是我们用于评价一个设计模式的重要指标之一。在设计模式中,很多设计模式都遵守了这些原则。 单一职责原则...
  • java面向对象程序设计的六(七)基本原则
  • 我对面向对象6大设计原则的理解

    千次阅读 2020-07-21 00:46:32
    说到面向对象,大家可能就会很快想到了 23 种设计模式,可只有少部分人会想到面向对象6 大原则,所以本文我分享一下我对于 6 大原则的看法。 6 大原则是内功心法,23 种设计模式是武术套路,它们的本质是为了更好...
  • 单一职责原则 定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。  虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在...
  • 带你了解面向对象的设计原则

    千次阅读 2016-05-23 23:21:59
    一 摘要今天晚上给大家介绍一下面向对象的设计原则,为什么要介绍这个呢,原因很简单,大家平时所接触的语言,无论是object-C,C++,JavaScrpt,C#等都是属于面向对象语言,既然是面向对象设计语言,那么就有必要去了解一下...
  • 1 软件设计模式的七大原则 1.1设计模式的目的 1.2设计模式七大原则 1.3单一职责原则 1.4 接口隔离原则(Interface Segregation Principle) 1.5 依赖倒转原则 1.6 里氏替换原则 1.7 开闭原则 1.8 迪米特法则 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 123,598
精华内容 49,439
关键字:

面向对象6大原则