原文:http://blog.donews.com/zchening/archive/2011/03/09/490.aspx
原文2:http://book.chinaunix.net/showart.php?id=8116
这是一个有图标的文件资源管理器,也许在网上的基于Android的market上有很多比较精美的文件资源管理器,这里我拿这个出来讲并不在于我做的界面如何的精美,而相反我这里的重点并不在界面,我只是想通过这么个列子和大家一起分享Android开发中的一下知识点:(1)目录的遍历(2)自定义Adapter(3)如何取得文件类型,以及调用系统打开对应的文件。这三点也是本程序的关键点所在,如果将这三个知识点掌握了,我想理解这个应用程序也就不再话下。
那么现在让我们一起来阅读代码吧,首先我们知道Android API提供的ArrayAdapter对象只允许存入String数组或List对象,所以在显示文件列表时,只能以一段字符串来显示文件的名称,如果要同时显示文件夹和文件的图标,以及文件名称,甚至文件类型、大小等信息,则必须要自定义一个实现Adapter Interface的对象,就可以自定义想要呈现的Layout,达到同时显示图片文件ICON与多个文字信息的效果,Android API中提供了BaseAdapter(Android.widget.BaseAdapter)对象,只要继承此对象就可以实现出属于自己的Adapter。
实现此范例时,须要先准备文件图标的ICON图片文件,并保存在/res/drawable/文件夹下,图片文件路径如下:
/* import程序略 */
public class EX05_15 extends ListActivity
{
/* 对象声明
items:存放显示的名称
paths:存放文件路径
rootPath:起始目录
*/
private List<String> items=null;
private List<String> paths=null;
private String rootPath="/";
private TextView mPath;
private View myView;
private EditText myEditText;
@Override
protected void onCreate(Bundle icicle)
{
super.onCreate(icicle);
/* 加载main.xml Layout */
setContentView(R.layout.main);
/* 初始化mPath,用以显示目前路径 */
mPath=(TextView)findViewById(R.id.mPath);
getFileDir(rootPath);
}
/* 取得文件架构的方法 */
private void getFileDir(String filePath)
{
/* 设置目前所在路径 */
mPath.setText(filePath);
items=new ArrayList<String>();
paths=new ArrayList<String>();
File f=new File(filePath);
File[] files=f.listFiles();
if(!filePath.equals(rootPath))
{
/* 第一笔设置为[回到根目录] */
items.add("b1");
paths.add(rootPath);
/* 第二笔设置为[回上层] */
items.add("b2");
paths.add(f.getParent());
}
/* 将所有文件添加到ArrayList中 */
for(int i=0;i<files.length;i++)
{
File file=files[i];
items.add(file.getName());
paths.add(file.getPath());
}
/* 使用自定义的MyAdapter来将数据传入ListActivity */
setListAdapter(new MyAdapter(this,items,paths));
}
/* 设置ListItem被按下时要做的动作 */
@Override
protected void onListItemClick(ListView l,View v,int position,
long id)
{
File file = new File(paths.get(position));
if(file.canRead())
{
if(file.isDirectory())
{
/* 如果是数据夹就再执行getFileDir() */
getFileDir(paths.get(position));
}
else
{
/* 如果是文件调用fileHandle() */
fileHandle(file);
}
}
else
{
/* 跳出AlertDialog显示权限不足 */
new AlertDialog.Builder(this)
.setTitle("Message")
.setMessage("权限不足!")
.setPositiveButton("OK",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog,int which)
{
}
}).show();
}
}
/* 处理文件的方法 */
private void fileHandle(final File file){
/* 按下文件时的OnClickListener */
OnClickListener listener1=new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog,int which)
{
if(which==0)
{
/* 选择的item为打开文件 */
openFile(file);
}
else if(which==1)
{
/* 选择的item为更改文件名 */
LayoutInflater factory=LayoutInflater.from(EX05_15.this);
/* 初始化myChoiceView,使用rename_alert_dialog为layout */
myView=factory.inflate(R.layout.rename_alert_dialog,null);
myEditText=(EditText)myView.findViewById(R.id.mEdit);
/* 将原始文件名先放入EditText中 */
myEditText.setText(file.getName());
/* new一个更改文件名的Dialog的确定按钮的listener */
OnClickListener listener2=
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
/* 取得修改后的文件路径 */
String modName=myEditText.getText().toString();
final String pFile=file.getParentFile().getPath()+"/";
final String newPath=pFile+modName;
/* 判断文件名是否已存在 */
if(new File(newPath).exists())
{
/* 排除修改文件名时没修改直接送出的情况 */
if(!modName.equals(file.getName()))
{
/* 跳出Alert警告文件名重复,并确认是否修改 */
new AlertDialog.Builder(EX05_15.this)
.setTitle("注意!")
.setMessage("文件名已经存在,是否要覆盖?")
.setPositiveButton("确定",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog,
int which)
{
/* 文件名重复仍然修改会覆盖掉已存在的文件 */
file.renameTo(new File(newPath));
/* 重新生成文件列表的ListView */
getFileDir(pFile);
}
})
.setNegativeButton("取消",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog,
int which)
{
}
}).show();
}
}
else
{
/* 文件名不存在,直接做修改动作 */
file.renameTo(new File(newPath));
/* 重新生成文件列表的ListView */
getFileDir(pFile);
}
}
};
/* create更改文件名时跳出的Dialog */
AlertDialog renameDialog=
new AlertDialog.Builder(EX05_15.this).create();
renameDialog.setView(myView);
/* 设置更改文件名按下确认后的Listener */
renameDialog.setButton("确定",listener2);
renameDialog.setButton2("取消",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
}
});
renameDialog.show();
}
else
{
/* 选择的item为删除文件 */
new AlertDialog.Builder(EX05_15.this).setTitle("注意!")
.setMessage("确定要删除文件吗?")
.setPositiveButton("确定",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog,
int which)
{
/* 删除文件 */
file.delete();
getFileDir(file.getParent());
}
})
.setNegativeButton("取消",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog,
int which)
{
}
}).show();
}
}
};
/* 选择一个文件时,跳出要如何处理文件的ListDialog */
String[] menu={"打开文件","更改文件名","删除文件"};
new AlertDialog.Builder(EX05_15.this)
.setTitle("你要做什么?")
.setItems(menu,listener1)
.setPositiveButton("取消",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
}
})
.show();
}
/* 在手机上打开文件的方法 */
private void openFile(File f)
{
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(android.content.Intent.ACTION_VIEW);
/* 调用getMIMEType()来取得MimeType */
String type = getMIMEType(f);
/* 设置intent的file与MimeType */
intent.setDataAndType(Uri.fromFile(f),type);
startActivity(intent);
}
/* 判断文件MimeType的方法 */
private String getMIMEType(File f)
{
String type="";
String fName=f.getName();
/* 取得扩展名 */
String end=fName.substring(fName.lastIndexOf(".")+1,
fName.length()).toLowerCase();
/* 根据扩展名的类型决定MimeType */
if(end.equals("m4a")||end.equals("mp3")||end.equals("mid")
||end.equals("xmf")||end.equals("ogg")||end.equals("wav"))
{
type = "audio";
}
else if(end.equals("3gp")||end.equals("mp4"))
{
type = "video";
}
else if(end.equals("jpg")||end.equals("gif")||end.equals("png")
||end.equals("jpeg")||end.equals("bmp"))
{
type = "image";
}
else
{
/* 如果无法直接打开,就跳出软件列表供用户选择 */
type="*";
}
type += "/*";
return type;
}
}
src/irdc.ex05_15/MyAdapter.java
自定义的Adapter对象,并以file_row.xml作为Layout,程序中依照文件的类型来决定要显示的图标是什么。
自定义的Adapter对象,并以file_row.xml作为Layout,程序中依照文件的类型来决定要显示的图标是什么。 file_row.xml/* import程序略 */
/* 自定义的Adapter,继承android.widget.BaseAdapter */
public class MyAdapter extends BaseAdapter
{
/* 变量声明
mIcon1:回到根目录的图片文件
mIcon2:回到上一层的图片
mIcon3:文件夹的图片文件
mIcon4:文件的图片
*/
private LayoutInflater mInflater;
private Bitmap mIcon1;
private Bitmap mIcon2;
private Bitmap mIcon3;
private Bitmap mIcon4;
private List<String> items;
private List<String> paths;
/* MyAdapter的构造器,传入三个参数 */
public MyAdapter(Context context,List<String> it,List<String> pa)
{
/* 参数初始化 */
mInflater = LayoutInflater.from(context);
items = it;
paths = pa;
mIcon1 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.back01);
mIcon2 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.back02);
mIcon3 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.folder);
mIcon4 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.doc);
}
/* 因继承BaseAdapter,需重写以下方法 */
@Override
public int getCount()
{
return items.size();
}
@Override
public Object getItem(int position)
{
return items.get(position);
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position,View convertView,ViewGroup par)
{
ViewHolder holder;
if(convertView == null)
{
/* 使用自定义的file_row作为Layout */
convertView = mInflater.inflate(R.layout.file_row, null);
/* 初始化holder的text与icon */
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
File f=new File(paths.get(position).toString());
/* 设置[回到根目录]的文字与icon */
if(items.get(position).toString().equals("b1"))
{
holder.text.setText("Back to /");
holder.icon.setImageBitmap(mIcon1);
}
/* 设置[回到上一层]的文字与icon */
else if(items.get(position).toString().equals("b2"))
{
holder.text.setText("Back to ..");
holder.icon.setImageBitmap(mIcon2);
}
/* 设置[文件或文件夹]的文字与icon */
else
{
holder.text.setText(f.getName());
if(f.isDirectory())
{
holder.icon.setImageBitmap(mIcon3);
}
else
{
holder.icon.setImageBitmap(mIcon4);
}
}
return convertView;
}
/* class ViewHolder */
private class ViewHolder
{
TextView text;
ImageView icon;
}
}
<!--?xml version="1.0" encoding="utf-8"?--> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <imageview android:id="@+id/icon" android:layout_width="30dip" android:layout_height="30dip"> </imageview> <textview android:id="@+id/text" android:layout_gravity="center_vertical" android:layout_width="0dip" android:layout_weight="1.0" android:layout_height="wrap_content" android:textcolor="@drawable/black"> </textview> </linearlayout>
写在最后:本范例重点在于如何通过实现自定义的Adapter对象来自定义想要呈现的Layout,以及如何在手机上实现打开文件的功能。
通过自定义的Adapter,可以在ListView中放入任何想要呈现的widget对象,如RadioButton、CheckBox、EditText等,如此一来,在开发程序时,就可以做更多样化得应用。
在主程序中自定义了openFile()这个方法来做打开文件的动作,程序内容如下
/* 调用getMIMEType()来取得MimeType */ String type = getMIMEType(f); /* 设置intent的file和MimeType */ intent.setDataAndType(Uri.fromFile(f),type); startActivity(intent);
其中使用intent.setDataAndType(Uri,type)来指定要打开的文件及文件的MIME Type,并以startActivity()的方式来打开文件。getMIMEType()这个方法中,依据文件的拓展名来设置文件的MIME Type,MIME Type格式为“文件类型/文件拓展名”,目前程序中针对部分类型的文件做MIME Type的判断,其余的文件则一律将MIME Type设置为“*/*”,当系统受到文件的类型为“*”时,会自动弹出应用程序的菜单,让用户自己选择要用哪个程序打开文件。
程序中使用java.io.File的renameTo(File newFile)来更改文件名称,需要注意的是,当修改后的文件名原本就已经存在时,程序会将原来的文件覆盖掉,且不会有任何的提示,这样等于是无意间删除了原来旧有的文件。为了预防这种状况发生,程序中先以file.exists()来判断是否有已经存在的文件,如果有,会先跳出警示的AlertDialog,请用户确认是否要覆盖旧文件。假如更改文件名,则可以预防文件无意间被删除的情况发生。
如果想让文件资源管理器的管理功能更强大,可以运用File对象提供的其他方法来实现,比如说,可用file.mkdir()来实现添加文件夹的功能;canRead()、canWrite()可以让文件资源管理器具备权限控制的功能。
程序中使用了许多的AlertDialog,Android API提供了android.app.AlertDialog.Builder对象,可以快速产生AlertDialog对象,以下介绍几种常用的方法,如表5-5所示。
表5-5 Mechod名称及功能
Method名称
- 1 Eclipse资源管理(4)
- 2 Eclipse资源管理(3)
- 3 Eclipse资源管理(2)
- 4 Eclipse资源管理(1)
- 5 资源管理中的方式
- 1 十分难缠的signal 11 (SIGSEGV)
- 2 Can't create handler inside thread that has not called Looper.prepare() 错误有关问题
- 3 Dex Loader Unable to execute Multiple dex files define解决办法
- 4 解决 Google Play下载施用 "Google Play Store 已停止运行"
- 5 WAP网页获得用户的手机号码
- 6 如何判断Activity是否在运行
- 7 SlidingMenu+ViewPager兑现侧滑菜单效果
- 8 makeKeyAndVisible的功用
- 9 关于Unable to execute dex: Java heap space 解决方法
- 10 RelativeLayout设置居中对齐有关问题
- 1 播发声音文件AVAudioPlayer
- 2 改变银屏显示方式已经加载图片
- 3 2013-十-31 TCP/IP 协议簇
- 4 Java I/零 总体框架图
- 5 拿碗的铠甲勇者
- 6 女友可能出轨 想知道在QQ和别人的聊天记录
- 7 objective C中的字符串(3)
- 8 java.lang.ClassNotFoundException: Didn't find class "Activity" on path: /da
- 9 LG Optimus G Pro 相干
- 10 怎么创建对话框
- 1 power键跟音量键组合实现截图功能
- 2 深入viewgroup.onintercepttouchevent1点
- 3 实现默认文字统制的textview
- 4 BroadcastReceiver要领
- 5 Andriod耗时操作的处置(音乐播放器欢迎界面)
- 6 MGTemplateEngine模版发动机
- 7 用 lipo 下令裁剪出需要的 architecture
- 8 疑惑为什么报错了
- 9 Tiledmap编辑操作技巧
- 10 视图切换的形式
- 上一篇: 软件工程师的无线互联创业陷阱
- 下一篇: abstract与interface的差异
- Web开发
- Web前端
- HTML/CSS
- PHP
- ASP
- JavaScript
- vbScript
- Ajax
- 网页设计
- 跨浏览器开发
- 高性能WEB开发
- Web Service
- XML/SOAP
- CGI
- 数据库
- SQL
- MySQL
- Oracle技术
- Oracle管理
- Oracle开发
- Oracle面试
- Oracle Exception
- Sql Server
- Informix
- Sybase
- DB2
- Access
- VFP
- 数据仓库
- 高性能数据库开发
- 其他数据库
- 移动开发
- Android
- Iphone
- Windows Mobile
- Symbian
- BlackBerry
- QT开发
- Brew
- MeeGo
- 移动平台
- 移动软件开发
- 电信IT应用开发
- 移动应用
- 企业开发
- 企业信息化
- 行业应用
- GIS
- SAP
- Tivoli
- Lotus
- Exchange
- SharePoint
- 报表
- 嵌入开发
- WinCE
- 硬件开发
- 单片机
- 汇编语言
- 驱动开发
- Wireless
- VxWorks
- Java Web开发
- J2EE
- J2SE
- J2ME
- Java面试
- Java相关
- Eclipse
- Java Exception
- 应用服务器
- Apache
- IIS
- JBoss
- WebSphere
- Weblogic
- ColdFusion
- 研发管理
- 项目管理
- 开发过程
- 开发方法
- 软件设计
- 设计模式
- 软件架构设计
- 敏捷开发
- 微创软件开发
- CVS/SVN
- VSTS
- PowerDesigner
- Rational
- 软件测试
- C#
- ASP.NET
- .NET Framework
- VB Dotnet
- VC
- .NET分析设计
- .NET组件控件
- J#
- Delphi
- .NET报表
- LINQ
- .NET新技术
- .NET面试
- .NET相关
- DotNet Exception
- Linux/Unix
- Solaris
- AIX
- 多媒体/流媒体开发
- 多媒体设计
- 交互式开发
- Flash
- 图形/图像
- 图像工具使用
- Flex
- AutoCAD
- Silverlight
- C++
- C语言
- C++ Builder
- VB
- PB
- Ruby/Rails
- perl/python
- 编程
- 其他开发语言
- 网络通信
- Open API
- 信息/网络安全
- IBM云计算
- Paypal
- VOIP
- Google技术
- 人工智能
- 搜索引擎
- CUDA
- 综合
- 互联网
- 操作系统
- 开源软件
- 共享软件
- 系统运维
- 高性能WEB开发
- 高性能数据库开发
- 高性能计算
- 多核软件开发
- 数据结构与算法
- 游戏开发
- 云计算
- 网络设计维护
- 数码设备
- 电脑整机及配件
- 装机与升级
- 外设及办公设备
- 电脑硬件
- 交换机/路由器
- Windows
- Windows2000
- Windows xp
- Windows7
- Office
- VBA
- VC/MFC
- Delphi
- 软件培训
- IT认证
- Oracle认证考试
- 软件水平考试
- IT课程
- 计算机图书
- 计算机英语
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。
理解抽象类
abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?
在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
从语法定义层面看abstract class和interface
在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。
使用abstract class的方式定义Demo抽象类的方式如下:
abstract class Demo {
abstract void method1();
abstract void method2();
…
}
使用interface的方式定义Demo抽象类的方式如下:
interface Demo {
void method1();
void method2();
…
}
在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。
从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。
首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。
在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。
同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。
从设计理念层面看abstract class和interface
上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。
前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door {
abstract void open();
abstract void close();
}
使用interface方式定义Door:
interface Door {
void open();
void close();
}
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。
如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的,并从设计理念层面对这些不同的方案进行分析。
一:
简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }
}
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。
二:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。
那位高手帮帮忙,给说一下NSMutableArray 和NSArray的,在用法上有什么不同?
NSMutableArray能添加、插入、删除对象,而NSArray不能
NSMutableArray 实现的是 NSMutableCopy protocol, NSArray 实现的是NSCopy protocol