`
xieyaxiong
  • 浏览: 38941 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

OOP从实例谈:面向对象编程编程思想

    博客分类:
  • oop
阅读更多

有了翅膀才能飞,欠缺灵活的代码就象冻坏了翅膀的鸟儿。不能飞翔,就少了几许灵动的气韵。我们需要给代码带去温暖的阳光,让僵冷的翅膀重新飞起来。结合实例,通过应用OOP、设计模式和重构,你会看到代码是怎样一步一步复活的。

    为了更好的理解设计思想,实例尽可能简单化。但随着需求的增加,程序将越来越复杂。此时就有修改设计的必要,重构和设计模式就可以派上用场了。最后当设计渐趋完美后,你会发现,即使需求不断增加,你也可以神清气闲,不用为代码设计而烦恼了。

    假定我们要设计一个媒体播放器。该媒体播放器目前只支持音频文件mp3和wav。如果不谈设计,设计出来的播放器可能很简单:

public class MediaPlayer
{  
   private void PlayMp3()
   {
      MessageBox.Show("Play the mp3 file.");
   }

   private void PlayWav()
   {
      MessageBox.Show("Play the wav file.");
   }

   public void Play(string audioType)
   {      
      switch (audioType.ToLower())
      {
          case ("mp3"):
             PlayMp3();
             break;
          case ("wav"):
             PlayWav();
             break;            
      }      
   }
}

 
    自然,你会发现这个设计非常的糟糕。因为它根本没有为未来的需求变更提供最起码的扩展。如果你的设计结果是这样,那么当你为应接不暇的需求变更而焦头烂额的时候,你可能更希望让这份设计到它应该去的地方,就是桌面的回收站。仔细分析这段代码,它其实是一种最古老的面向结构的设计。如果你要播放的不仅仅是 mp3和wav,你会不断地增加相应地播放方法,然后让switch子句越来越长,直至达到你视线看不到的地步。

好吧,我们先来体验对象的精神。根据OOP的思想,我们应该把mp3和wav看作是一个独立的对象。那么是这样吗?

public class MP3
{
   public void Play()
   {
       MessageBox.Show("Play the mp3 file.");
   }
}

public class WAV
{
   public void Play()
   {
       MessageBox.Show("Play the wav file.");
   }
}

 

    好样的,你已经知道怎么建立对象了。更可喜的是,你在不知不觉中应用了重构的方法,把原来那个垃圾设计中的方法名字改为了统一的Play()方法。你在后面的设计中,会发现这样改名是多么的关键!但似乎你并没有击中要害,以现在的方式去更改MediaPlayer的代码,实质并没有多大的变化。

既然mp3和wav都属于音频文件,他们都具有音频文件的共性,为什么不为它们建立一个共同的父类呢?

public class AudioMedia
{
   public void Play()
   {
       MessageBox.Show("Play the AudioMedia file.");
   }
}

 
现在我们引入了继承的思想,OOP也算是象模象样了。得意之余,还是认真分析现实世界吧。其实在现实生活中,我们播放的只会是某种具体类型的音频文件,因此这个AudioMedia类并没有实际使用的情况。对应在设计中,就是:这个类永远不会被实例化。所以,还得动一下手术,将其改为抽象类。好了,现在的代码有点OOP的感觉了:

public abstract class AudioMedia
{
   public abstract void Play();
}

public class MP3:AudioMedia
{
   public override void Play()
   {
       MessageBox.Show("Play the mp3 file.");
   }
}

public class WAV:AudioMedia
{
   public override void Play()
   {
       MessageBox.Show("Play the wav file.");
   }
}

public class MediaPlayer
{  
   public void Play(AudioMedia media)
   {      
       media.Play();
   }
}

 
    看看现在的设计,即满足了类之间的层次关系,同时又保证了类的最小化原则,更利于扩展(到这里,你会发现play方法名改得多有必要)。即使你现在又增加了对WMA文件的播放,只需要设计WMA类,并继承AudioMedia,重写Play方法就可以了,MediaPlayer类对象的Play方法根本不用改变。

    是不是到此就该画上圆满的句号呢?然后刁钻的客户是永远不会满足的,他们在抱怨这个媒体播放器了。因为他们不想在看足球比赛的时候,只听到主持人的解说,他们更渴望看到足球明星在球场奔跑的英姿。也就是说,他们希望你的媒体播放器能够支持视频文件。你又该痛苦了,因为在更改硬件设计的同时,原来的软件设计结构似乎出了问题。因为视频文件和音频文件有很多不同的地方,你可不能偷懒,让视频文件对象认音频文件作父亲啊。你需要为视频文件设计另外的类对象了,假设我们支持RM和MPEG格式的视频:

public abstract class VideoMedia
{
   public abstract void Play();
}

public class RM:VideoMedia
{
   public override void Play()
   {
       MessageBox.Show("Play the rm file.");
   }
}

public class MPEG:VideoMedia
{
   public override void Play()
   {
       MessageBox.Show("Play the mpeg file.");
   }
}

 
    糟糕的是,你不能一劳永逸地享受原有的MediaPlayer类了。因为你要播放的RM文件并不是AudioMedia的子类。

    不过不用着急,因为接口这个利器你还没有用上(虽然你也可以用抽象类,但在C#里只支持类的单继承)。虽然视频和音频格式不同,别忘了,他们都是媒体中的一种,很多时候,他们有许多相似的功能,比如播放。根据接口的定义,你完全可以将相同功能的一系列对象实现同一个接口:

public interface IMedia
{
   void Play();
}

public abstract class AudioMedia:IMedia
{
   public abstract void Play();
}

public abstract class VideoMedia:IMedia
{
   public abstract void Play();
}

 
再更改一下MediaPlayer的设计就OK了:

public class MediaPlayer
{  
   public void Play(IMedia media)
   {      
       media.Play();
   }
}

 
   现在可以总结一下,从MediaPlayer类的演变,我们可以得出这样一个结论:在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,而应传递其抽象对象,更好地是传递接口,将实际的调用和具体对象完全剥离开,这样可以提高代码的灵活性。

     不过,事情并没有完。虽然一切看起来都很完美了,但我们忽略了这个事实,就是忘记了MediaPlayer的调用者。还记得文章最开始的switch语句吗?看起来我们已经非常漂亮地除掉了这个烦恼。事实上,我在这里玩了一个诡计,将switch语句延后了。虽然在MediaPlayer中,代码显得干净利落,其实烦恼只不过是转嫁到了MediaPlayer的调用者那里。例如,在主程序界面中:

public void BtnPlay_Click(object sender,EventArgs e)
{
    switch (cbbMediaType.SelectItem.ToString().ToLower())
    {
        IMedia media;
        case ("mp3"):
             media = new MP3();
             break;
        case ("wav"):
             media = new WAV();
             break;  
        //其它类型略;
    }
    MediaPlayer player = new MediaPlayer();
    player.Play(media);
}

 

用户通过选择cbbMediaType组合框的选项,决定播放哪一种文件,然后单击Play按钮执行。

    现在该设计模式粉墨登场了,这种根据不同情况创建不同类型的方式,工厂模式是最拿手的。先看看我们的工厂需要生产哪些产品呢?虽然这里有两种不同类型的媒体 AudioMedia和VideoMedia(以后可能更多),但它们同时又都实现IMedia接口,所以我们可以将其视为一种产品,用工厂方法模式就可以了。首先是工厂接口:

public interface IMediaFactory
{
   IMedia CreateMedia();
}

 
然后为每种媒体文件对象搭建一个工厂,并统一实现工厂接口:

public class MP3MediaFactory:IMediaFactory
{
   public IMedia CreateMedia()
   {
       return new MP3();
   }
}
public class RMMediaFactory:IMediaFactory
{
   public IMedia CreateMedia()
   {
       return new RM();
   }
} 

  


//其它工厂略;

     写到这里,也许有人会问,为什么不直接给AudioMedia和VideoMedia类搭建工厂呢?很简单,因为在AudioMedia和 VideoMedia中,分别还有不同的类型派生,如果为它们搭建工厂,则在CreateMedia()方法中,仍然要使用Switch语句。而且既然这两个类都实现了IMedia接口,可以认为是一种类型,为什么还要那么麻烦去请动抽象工厂模式,来生成两类产品呢?

     可能还会有人问,即使你使用这种方式,那么在判断具体创建哪个工厂的时候,不是也要用到switch语句吗?我承认这种看法是对的。不过使用工厂模式,其直接好处并非是要解决 switch语句的难题,而是要延迟对象的生成,以保证的代码的灵活性。当然,我还有最后一招杀手锏没有使出来,到后面你会发现,switch语句其实会完全消失。

    还有一个问题,就是真的有必要实现AudioMedia和VideoMedia两个抽象类吗?让其子类直接实现接口不更简单?对于本文提到的需求,我想你是对的,但不排除AudioMedia和VideoMedia它们还会存在区别。例如音频文件只需要提供给声卡的接口,而视频文件还需要提供给显卡的接口。如果让MP3、WAV、RM、MPEG直接实现IMedia接口,而不通过AudioMedia和VideoMedia,在满足其它需求的设计上也是不合理的。当然这已经不包括在本文的范畴了。

现在主程序界面发生了稍许的改变:

public void BtnPlay_Click(object sender,EventArgs e)
{
IMediaFactory factory = null;
    switch (cbbMediaType.SelectItem.ToString().ToLower())
    {
        case ("mp3"):
             factory = new MP3MediaFactory();
             break;
        case ("wav"):
             factory = new WAVMediaFactory();
             break;  
        //其他类型略;
    }
    MediaPlayer player = new MediaPlayer();
    player.Play(factory.CreateMedia());
}

 
    写到这里,我们再回过头来看MediaPlayer类。这个类中,实现了Play方法,并根据传递的参数,调用相应媒体文件的Play方法。在没有工厂对象的时候,看起来这个类对象运行得很好。如果是作为一个类库或组件设计者来看,他提供了这样一个接口,供主界面程序员调用。然而在引入工厂模式后,在里面使用MediaPlayer类已经多余了。所以,我们要记住的是,重构并不仅仅是往原来的代码添加新的内容。当我们发现一些不必要的设计时,还需要果断地删掉这些冗余代码。

Public void BtnPlay_Click(object sender,EventArgs e)
{
IMediaFactory factory = null;
    switch (cbbMediaType.SelectItem.ToString().ToLower())
    {        
        case ("mp3"):
             factory = new MP3MediaFactory();
             break;
        case ("wav"):
             factory = new WAVMediaFactory();
             break;  
        //其他类型略;
    }
    IMedia media = factory.CreateMedia();
    media.Play();
} 

  



    如果你在最开始没有体会到IMedia接口的好处,在这里你应该已经明白了。我们在工厂中用到了该接口;而在主程序中,仍然要使用该接口。使用接口有什么好处?那就是你的主程序可以在没有具体业务类的时候,同样可以编译通过。因此,即使你增加了新的业务,你的主程序是不用改动的。

    不过,现在看起来,这个不用改动主程序的理想,依然没有完成。看到了吗?在BtnPlay_Click()中,依然用new创建了一些具体类的实例。如果没有完全和具体类分开,一旦更改了具体类的业务,例如增加了新的工厂类,仍然需要改变主程序,何况讨厌的switch语句仍然存在,它好像是翅膀上滋生的毒瘤,提示我们,虽然翅膀已经从僵冷的世界里复活,但这双翅膀还是有病的,并不能正常地飞翔。

    是使用配置文件的时候了。我们可以把每种媒体文件类类型的相应信息放在配置文件中,然后根据配置文件来选择创建具体的对象。并且,这种创建对象的方法将使用反射来完成。首先,创建配置文件:


  
  
  
  


    然后,在主程序界面的Form_Load事件中,读取配置文件的所有key值,填充cbbMediaType组合框控件:

public void Form_Load(object sender, EventArgs e)
{
     cbbMediaType.Items.Clear();
     foreach (string key in ConfigurationSettings.AppSettings.AllKeys)
    {
        cbbMediaType.Item.Add(key);
     }
     cbbMediaType.SelectedIndex = 0;
}

最后,更改主程序的Play按钮单击事件:
Public void BtnPlay_Click(object sender,EventArgs e)
{
    string mediaType = cbbMediaType.SelectItem.ToString().ToLower();
     string factoryDllName = ConfigurationSettings.AppSettings 
     [mediaType].ToString();
     IMediaFactory factory = (IMediaFactory)Activator.CreateInstance 
     ("MediaLibrary",factoryDllName).Unwrap ();//MediaLibray为引用的媒体文件及工厂的程序集;
      IMedia media = factory.CreateMedia();
      media.Play();
}

 
现在鸟儿的翅膀不仅仅复活,有了可以飞的能力;同时我们还赋予这双翅膀更强的功能,它可以飞得更高,飞得更远!

享受自由飞翔的惬意吧。设想一下,如果我们要增加某种媒体文件的播放功能,如AVI文件。那么,我们只需要在原来的业务程序集中创建AVI类,并实现 IMedia接口,同时继承VideoMedia类。另外在工厂业务中创建AVIMediaFactory类,并实现IMediaFactory接口。假设这个新的工厂类型为WingProject.AVIFactory,则在配置文件中添加如下一行:

而主程序呢?根本不需要做任何改变,甚至不用重新编译,这双翅膀照样可以自如地飞行!

 

 

【转载地址:】:http://houliang4285.iteye.com/blog/981589

分享到:
评论

相关推荐

    从实例谈面向对象编程、工厂模式和重构

    根据 OOP 的思想,我们应该把 mp3 和 wav 看作是一个独立的对象。那么 是这样吗? 程序代码 public class MP3 { public void Play() { MessageBox.Show("Play the mp3 file."); } } public class WAV { ...

    PHP面向对象各个功能步骤详解OOP思想

    3.什么是面向对象编程呢? 4.如何抽象出一个类? 5.如何实例化对象 6.如何去使用对象中的成员 7.特殊的引用“$this“的使用 8.构造方法与析构方法 9.封装性 10.__set() __get() __isset() __unset()四个方法的应用 ...

    从实例谈OOP、工厂模式和重构

    结合实例,通过应用OOP、设计模式和重构,你会看到代码是怎样一步一步复活的。为了更好的理解设计思想,实例尽可能简单化。但随着需求的增加,程序将越来越复杂。此时就有修改设计的必要,重构和设计模式就可以派上...

    PHP面向对象编程快速入门

    【摘 要】面向对象编程(OOP)是我们编程的一项基本技能,PHP4对OOP提供了良 好的支持。如何使用OOP的思想来进行PHP的高级编程,对于提高PHP编程能力和 规划好Web开发构架都是非常有意义的。 面向对象编程(OOP)是...

    跟我学Java面向对象程序设计技术及应用——Java中的面向对象技术(第1部分).pdf

    2) 面向对象编程的四个基本机制是什么? 3) 你知道 java 语言在面向对象编程方面有何独特的特点吗? 1.1.1 面向对象技术基础 1、面向对象(OO)的基本概念 (1)面向对象的基本思想 面向对象是一种新兴的程序设计...

    PHP100视频教程 17:PHP面向对象开发的学习(一).rar

    面向对象的思想已经涉及到软件开发的各个方 面。如,面向对象的分析(OOA,Object Oriented Analysis),面向对象的设计 (OOD,Object Oriented Design)、以及我们经常说的面向对象的编程实现 (OOP,Object ...

    PHP100视频教程 17:PHP面向对象开发的学习(一)

    如,面向对象的分析(OOA,Object Oriented Analysis),面向对象的设计(OOD,Object Oriented Design)、以及我们经常说的面向对象的编程实现(OOP,Object Oriented Program)。类我们可以理解成一个功能集合菜单...

    简述Python中的面向对象编程的概念

    面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组...

    免费下载:c++编程思想(中文)

    6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 ...

    C++编程思想 编程

    C++编程思想,目 录 译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:类型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 3 1.2.1 较好的C 3 ...

    java面向对象程序设计.pptx

    4.1.1 OOP思想 面向对象编程技术 将数据及对数据的操作封装在一起而形成了类,类是描述相同类型的对象集合。 。面向对象编程(object-oriented programming,OOP)就是定义这些类。 类作为抽象的数据类型用于创建类...

    php类实例教程让更多的PHPer开始转向OO的编程过程

    希望这个介绍PHP5面向对象编程(OOP)的资料能让初学者受益,能让更多的PHPer开始转向OO的编程过程。 相对PHP4,PHP5在面向对象方面改变了很多。我们将只介绍PHP5环境下的面向对象。而我们必须改变自己来跟随PHP5的...

    python学习之面向对象【入门初级篇】

    最近在学习Python的面向对象编程,以前是没有接触过其它的面向对象编程的语言,因此学习这一部分是相当带劲的,这里也总结一下。 概述 python支持多种编程范式:面向过程、面向对象、面向切面(装饰器部分)等。 ...

    C++编程思想.pdf

    《C++编程思想》,原名《Thinking in C++》,作者:Bruce Eckel,翻译:刘宗田等,出版社:机械工业出版社,ISBN:7111071166,pdf 格式,大小 4MB。本资源仅包括前七章的内容。 内容简介: 本书作者根据自己学习C++...

    c++ 编程思想 (高清pdf)

    6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 ...

    python基础2.0版(6)— 面向对象第一部分

    文章目录思维导图一、面向对象编程二、面向对象和面向过程区别1.面向过程(Procedure Oriented)思维2.面向对象(Object Oriented)思维3.面向对象和面向程的总结三、对象的进化四、类的定义五、构造函数__init__()1...

    python面向对象程序设计

    目录 面向对象和面向过程区别 类和对象 1.定义类 2.创建类的实例 3.创建类的成员并访问 ...面向对象程序设计:(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的

    C++编程思想(中文版) chm

    本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流...

Global site tag (gtag.js) - Google Analytics