抽象与分解是面向对象编程中的基本方法。人类理解问题的方式通常是抽象出一类问题的定义,然后根据这个定义来认识问题。通常抽象可分3类:
1. 抽象名词定义:
就是大家已经对一些社会或自然界已经存在的事物已经有了抽象的定义了。比如森林,森林是个抽象的定义,表示有很多不同树木和其他植物组成的地方。如果我们程序里要表示森林,可以这样:
class Tree{}; class Grass{};
class ATree:public Tree{}: class BTree:public Tree{};
class Forest{
.......................
vector<Tree*> m_cTrees;
vector<Grass*> m_cGrasses;
};
2.抽象行为定义
就是对某种特定的行为进行概括定义。比如羊吃草,狼吃羊,都可以概括为动物进食,我们可以在程序里这样表示:
class Animal{
virtual void feed()=0;
} ;
class Sheep: Animal {
void feed()
{
printf("eat grass");
}
class Wolf: public Animal {
void feed(){
printf ("eat sheep");
}
};
3. 自定义抽象
除开1,2外的抽象都可以归结为此类。 软件中不是所有的概念和行为大家都有确定的定义。比如我们要统一处理森林里的所以动物,那么为了方便处理,我们通常可以定义一个vector<Animal*> 对象,
但是为了方便理解和处理,我们可以将其抽象为动物管理器:
class AnimalMgr {
vector<Animal*> m_cAnimals;
public:
int GetMaxAnimal() ; // 找到数量最多的动物,返回其在数组中的位置
void PushNewAnimal(Animal* pAn); //增加新的种类
};
我们通过一个简单的文件读取工作来实际了解下抽象的运用:
比如1. 从一个文件中读取出字符,并打印出来,但只需要处理字符等于 'a' 或 'c'的字符。这个问题很简单,基本用不到抽象:
void workForFile(char* filename)
{
f = fopen(fiename);
while(c = readchar(f))
{
if( c =='a' || c=='c')
printf(c);
}
}
2. 客户对需求有了变化,要求字符等于 'a' 或 'c'或等于"bd"或“db" 于是,我们修改如下:
void workForFile(char* filename)
{
f=fopen(filename);
string s ;
while(c=readchar(f))
{
s+=c;
if(*s.data() = 'a' ||*s.data()=='c' || strcmp(s.data(),"bd") ==0|| strcmp(s.dat(),"db")==0)
printf(c);
}
}
看起来这里对字符串的过滤是个容易变化的东西,并且还有一点复杂, 我们修改如下:
bool Filt(string s)
{
if(*s.data() = 'a' ||*s.data()=='c' || strcmp(s.data(),"bd") ==0|| strcmp(s.dat(),"db")==0)
return true;
return false;
}
void workForFile(char* filename)
{
f=fopen(filename);
string s ;
while(c=readchar(f))
{
s+=c;
if(Filt(s))
printf(c);
}
}
我们把过滤行为抽象为Filt 函数 ,这样我们的workForFile 就被抽象为打开文件,读取字符,过滤字符,与打印字符 4个行为,而与具体的a,c,bd 过滤方式无关,这样 workForFile函数看起来更简单,容易理解。更重要的是,workforfile 函数经过抽象后, 更稳定了,与变化无关了(过滤字符的变化只需要修改filt)。这个就是OO原则:依赖抽象不依赖具体的
一个很好的理由。因为抽象更稳定。
3. 当客户需要动态改变过滤方式的时候, 比如A 文件 需要过滤 'a',B文件则只需要过滤'b', 这种情况我们提供的filt函数好像不够用了, 我们需要多提供
一个函数:
typdef void (*pfnFilt) (string s) ;
void FiltA(string s)
{
...
};
void FiltB(string s)
{
......
}
void workForFile(char* filename,pfnFilt )
{
f=fopen(filename);
string s ;
while(c=readchar(f))
{
s+=c;
if(pfnFilt(s))
printf(c);
}
}
int main()
{
workForFile("a",FiltA);
workForFile("b",FiltB);
return 0;
}
这里我们将Filt A,FiltB 抽象为 一个提供过滤功能的函数指针,如果我们将其用类来表示:
class Filter {
public:
virtual bool Filt(string s)=0;
};
class FilterA:public Filter
{public:
bool Filter(string s)
{
return FiltA();
}
};
class FilterB:public Filter
{public:
bool Filt(string s)
{
return FiltB(s);
}
}
void workForFile(char* filename,Filter* pFilter)
{
f=fopen(filename);
string s ;
while(c=readchar(f))
{
s+=c;
if(pFilter->Filt(s))
printf(c);
}
}
4. 如果客户不想仅仅只是处理文件,还要求可以处理网络上传入的数据.则我们将文件与网络数据处理抽象为IO处理类:
class IoProcess{
protected:
HANDHLE hIo;
Filter * m_pFilter;
public:
virtual char ReadChar() =0 ;读取行为被抽象出来了, 因为可能读的方式有所不同
virtual void Work () // 看上去,这个很稳定,没有什么需要修改的,所以可以放到这里
{
string s ;
while(c=ReadChar(m_hIo))
{
s+=c;
if(pFilter->Filt(s))
printf(c);
}
}
}
class FileProcess:public IoProcess{
FileProcess(char* pfile,Filter* pFilter){
hIo = fopen(pfile):
m_pFilter = pFilter;
};
{
.....
}
};
class NetProcess:public IoProcess{
NetProcess(socket s,Filter * pFilter)
{
hIo = s;
m_pFilter = pFilter;
}
char ReadFile()
{...
}
};
int main()
{
IoProcess * p = new FileProcess( "a", new FilterA());
IoPorcess* pp = new NetProcess(sock ,new FilterB());
}
这里看上去与设计模式的 BRIDGE 有很像,嘿嘿 ,所以设计模式也是抽象的某种特例吧。