工厂、单例、宏

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"></span>


最近看了设计模式的书,看了一部分忍住没有继续看,因为觉得前面的部分需要好好消化。消化的方法就是写代码,实现,但是一直没有有意思且能体现出设计模式的项目。

前几天,在作笔记的时候,萌生了一个点子:如果我写纯文本的时候,想引用别的笔记、或者插入一张图片怎么做?确实有很多方便的软件可以插入,并且实时显示,但作为一个程序员,比较喜欢纯文本的编辑方式。想了一会儿,觉得这就类似于latex,当然我没那本事写个那么强大的软件,但我想到一个很常见很方便生成的东西——网页。

写笔记,然后生成HTML语句,在网页中显示,确实是个方法,于是我就动手写了点框架,突然发现似乎可以用到设计模式。而我首先想到的是工厂模式。

1. 工厂模式

 工厂模式,简单来说就是利用一个“工厂”类的方法来生成实例,而不是使用直接实例化的方法。

比如,假设有个类A,如果要实例化一个对象,我们一般采用如下方法:

A a = A();
A*b = new A;

这种方法的弊端在于,相似类型的类一多,每次实例化都要指定不同的类型,会非常麻烦,于是可以使用工厂模式改成下面这样:

AA a = Factory::create(1);
AB b = Factory::create(2);
....
对象的产生就是可控的了。

关于工厂模式就暂时简说这点。更详细的内容网上都很丰富。


在我的程序里面,我对不同HTML元素的创建使用了工厂模式,首先建立了一个工厂类,方法为create,它会根据输入字符串模式返回不同类型的HTML语句对象。但在这里我突然想到一点:对不同类型的HTML语句,其实它们的模式是一样的,根本用不着每次都实例化它一个对象啊。

比如说我要产生插入图片的HTML语句:

<img src = "ball.gif"/>
只需要提供文件名等信息(其他信息代码中略去了),然后产生字符串就行了,这样一来,每次去实例化对象就毫无意义了。它单纯只需要提供一个格式化字符串的方法就行了。

于是我想到了单例模式。

2. 单例模式

单例模式,主要用于一个程序只需要一个对象的情况。比如,这个类中管理着很大量的数据,而且只需存这一份,为了防止被实例化——因为一旦被程序员误用实例化了,会耗费大量资源,可以把构造函数设为private的,然后使用静态函数访问,并返回静态指针变量。

class A{
private:A*me;
A(){}
public:A*GetInstance(){
    if(me==nullptr)me = new A();
    return me;
}
};
这样一来,就能保证实例的唯一性,资源就不会被误用而浪费了,在程序任何一地方需要用到对象,只需要通过GetInstance()函数访问指针即可。

//===========「小心陷阱」===========

这里要注意静态变量的使用,在之前一篇文章里我提到了,作为类成员的静态变量在内部声明了,需要再在外部定义才能够使用。而在定义的时候,尽量把静态变量的定义拿到cpp实现文件中(针对c/c++),如果不这样做,把定义放在头文件里的话,在同一工程,两个cpp文件调用同一头文件时,就会发生重定义。

//================================

有了单例模式这个利器,配合上基类接口,很容易就实现对不同方法的调用。就像这样:

if(/*正则表达式匹配为插入图片语句*/){
  Chtml*p = Cimg::getInstance();
  return p->create();
}
if(....

额,好像是那么回事。不过要是语句类型多了,这代码得写多长啊。简化代码的方法,我只想到查表,对不同的匹配模式,一个循环即可(伪码):

for each pattern_key in map_table{
    if(/*key匹配*/)return map_table[pattern_key];
}
似乎是可行的(具体实现细节略,使用了C++模板map以及regex等),但这个代码是简化了,类声明的代码却没法简化,每次都得写一堆重复的单例模式内容,虽说可以复制粘贴,但是看着一堆重复内容也不爽啊。
于是想到了大杀器——宏。


3. 宏

宏不是设计模式,是一种预处理模式。

比如我这样使用:

#define HTML_ELEM(name) class C##name##:public:Chtml\
{...//同上classA}

这样我只需要一句:HTML_ELEM(img); 就声明了一个类!当然,这样做只是为了代码简洁,实际上代码编译过程中,这些地方还是被生成长长的类声明的。

这样一来,只需要在实现文件编写不重复的部分就行了,代码无比清爽!


最后,简洁强迫症的我还是发现一个巨大无比的问题:表的初始化。

之前,我在表中存储了关键字和对应的对象指针,方便进行判断使用哪一个语句来生成。但是,这CHTML子类声明写得越多,表的初始化就越麻烦,得一个个、一行行赋值地初始化!

为什么不能让程序自己判断呢?因为程序在运行时,并不能自己直接找到对象指针,除非显示调用GetInstance,才可以!写到这里,似乎单例模式又把我坑了……

作为坚决不重构党,我冥思苦想,想到了一个奇葩却又行之有效的方法。

「想思考的可以先不看答案」

我们已知:程序员写的区域只能可以调用一个我们写好的定义宏。该程序员要写新的类时,要告诉我们那个类的名字(也就是说我们只知道这个信息,我们会根据这个信息使用宏来声明一个类),然后他在自己的实现文件里调用我们写好的宏来定义类里的静态变量(静态变量是对象指针,因此初始化工作程序员无需关心,默认初始化为NULL)

我们:CHTML_ELEM(name)//name是程序员告诉我们的名字

程序员:CHTML_DEFINE(name)

.....//其他实现


我们不能修改的位置:客户区(客户区就是查询表得出结果的部分)、程序员实现区。

目标是:不让客户区使用代码而完成对表的初始化(表可简化为一个线性表,其中只装着各个对象的指针)。


这看上去是不可能的。因为在客户区之前就是类声明,不进客户区的函数体,怎样实现表的初始化呢?而且还是未知个表数目?



……

我们都知道,语句只能写在函数体里,函数体外是不能写的,函数体外只能写声明、写定义。

啊,是的,定义,嗯?没错,定义时可以调用函数!

因此,我想到的方法是这样的:

因为程序员总要调用我们的一个宏,用来定义静态对象指针,因此我们可以修改宏,在后面加一句:

char ctemp##name = table_add(C##name##::GetInstance())

嗯?你说char是什么鬼?额,其实这个char变量并没有什么用,当然如果你要让它有用也可以,但这并不是我们的终点,我们的重点在于table_add()这个函数,很明显它是静态函数,它的作用当然不是用来定义char废变量的,而是利用char变量作跳板,往表中添加元素!

等程序员写完类,编译运行,客户区就会有初始化好的表了。




有好的意见,或者更好的方案,欢迎指正提出。

我们都知道,语句只能写在函数体里,函数体外是不能写的,函数体外只能写声明、写定义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值