=================================版权声明=================================

版权声明:原创文章 禁止转载 

请通过右侧公告中的“联系邮箱(wlsandwho@foxmail.com)”联系我

勿用于学术性引用。

勿用于商业出版、商业印刷、商业引用以及其他商业用途。                   

 

本文不定期修正完善。

本文链接:http://www.cnblogs.com/wlsandwho/p/5371102.html

耻辱墙:http://www.cnblogs.com/wlsandwho/p/4206472.html

=======================================================================

其实Singleton模式,很多人都写过类似的博客。

虽然阅读量很多很多,可能超过了我所有博客的阅读量,但是没几个的代码是能用的。

而且也没有说为什么要用这种东西。

  也没说在什么情况下要用这种东西。

 

本文不讲解原理,详细请参见四人帮的经典书籍。我相信原理的东西,我和大师还是有差距的。

这里只讲点自己的看法。

 

有什么不对的地方欢迎指正。不喜欢来咬我呀。

=======================================================================

下面王林森举两个最常见的例子,以说明为什么要用Singleton模式。

=======================================================================

1 一个在各个文件里都需要用到的变量

作为一只MFCer,以对话框程序为例,我们知道有一个叫theApp的实例。(什么你不是?那这个例子和这篇文章的代码不适合你。:)

这个东西在WLSApp.h中用extern声明,

    在WLSApp.cpp中定义。

theApp实例可以在CWLSDlg中使用。(因为.h文件有声明,.cpp文件里有定义,实在想不通可以把.h和.cpp合成一个.hpp)

 

现在假如王林森在该对话框程序里又添加了另一个对话框CWLSDlg2,(用作其他弹出式界面),

那么如果CWLSDlg2要使用theApp,

就必须在CWLSDlg2中引用WLSApp.h文件。

 

现在再假如王林森在程序初始化的时候读取了一些配置用于稍后所有的界面初始化,比方说CMultiLanguage的一个实例oML用作多语言支持,

那么如果想要所有的界面都使用同一个oML,

就需要像theApp一样,在WLSApp.h中引用MultiLanguage.h,

            在WLSApp.h中用extern声明,

            在CWLSApp.cpp中定义,

            在所有打算使用oML的文件里,引用WLSApp.h

(讲道理,其实不是必须在WLSApp.h文件里,之所以要这么做,是因为跟theApp的定义声明放在一起比较好。)

 

这是一个很繁琐的过程,而且你不能保证在所有的地方使用的是同一个。(一旦繁琐,就可能出错。)

=======================================================================

2 一个在多线程环境中初始化并使用的变量

假如我们要处理一些数据,对记录中间结果那个变量的初始化工作是其中一个线程做的,但如果其余线程也空闲着,那么可以帮助这个线程一起处理数据。

(这个例子可能不恰当。)

 

显然我们需要保证大家用的是一个同一个变量,既不能对同一份数据生成多个变量,也不能用不同的变量处理同一份数据。

=======================================================================

上面是可能用到Singleton的情况。

=======================================================================

有一种情况,记住不要用Singleton,那就是:

  需要使用同一个变量,

  在一个文件里定义,(可以在其头文件里声明)

  并且只在这个文件里使用。

因为:

  这种情况就应该用全局变量啊。(在这个文件的开始定义一个全局变量。)

 

(什么?老师说“XXXXX”?老师还说“XXXXX”?好,你赢了。)

=======================================================================

搞清楚了这些,我们再继续进行。

=======================================================================

以下以多语言支持CMultiLanguage为例。

(用多语言支持来举例,是因为我想把我的设计模式系列写成有实际能用例子的系列,而不是那些做烧烤做遥控器之类的很奇怪的例子。)

常见的Singleton实现,有多种情况,这里举两个:

1 在GetInstance里返回一个static CMultiLanguage oML。(这个要在.cpp文件里定义并初始化。屏蔽拷贝构造和赋值运算符等。)

2 在GetInstance里,通过判断指针是否为空,返回一个通过new CMultiLanguage的poML。

 

坊间传闻C++11可以在第1种的代码里不加锁,我不是很清楚,对此没有探讨过。(下面讨论加锁。)

作为VS2010SP1用户,我通常使用第2种。

=======================================================================

对于加锁,好多博客里只写了个Lock和UnLock,然后一个注释“这里加锁”、“这里解锁”。

这种代码你肯定跑不了的好吗?!

 

还有的博客是用了内部类,利用构造函数加锁,析构函数解锁。这个想法很不错,但是大多数的代码也用不了。

因为你传参了但是没用到啊,你没用到同一个临界区怎么知道是加了锁了?

你怎么保证你外部传进来的作为参数的临界区是同一个?怎么保证这个临界区的生命期?

另外个人用个人的锁,那不叫加锁啊。大家用同一把锁那才叫加锁啊。

 

所以,同时作为VS用户和MFC用户,我会使用一个CCriticalSection,把它typedef为CLock。

                    声明一个static的CLock成员对象,在.cpp文件里定义。(这里注意要有类名进行限定修饰,不然会不识别。)

                    判断指针是否为空,加锁,判断指针是否为空,分配,解锁。(坊间传闻这个叫双重加锁?我觉得名字起的不好。)

搞定!

=======================================================================

再来说下垃圾回收。

可能有人会说这个变量的使用贯穿始终,不需要啊。

 

是的,如果能做到只在主程序/主界面里使用,还真的不需要,在整个程序结束的时候,任何资源都会被回收。

 

可是,如果这个变量是用在一个经常创建并销毁的地方,那么就要当心了。

比方说,变量定义在一个弹出的子对话框上,这个对话框有三个选项卡,它们共用这个变量。

不停的关闭和打开这个字对话框,会不断的创建和销毁变量。如果不回收内存,就会不断的造成内存泄漏。直至内存耗尽然后一步一卡。

 

在垃圾回收上,比较赞同用内部类的方式处理。

使用一个内部类的static修饰的变量,例如static CAutoDeleter m_oAD,然后再在.cpp文件中定义并初始化。(因为大家都知道的原因,static是类拥有而不是变量拥有的。)

=======================================================================

大体的需要注意的地方就这些。

下面来具体的举一个实际的例子。

=======================================================================

多语言支持,是一个不可避免的话题。我们的征途是星辰大海,必然要走出国门,黑白头鹰、骆驼、汉斯猫以及其他动物的。

然而在我们没有走出国门前,经常会忽视掉/忘记这个功能模块。

 

那么问题来了:

后期如何添加?

怎么能支持扩展语言?

把要做的发给外贸的同事,他们不会编程,能改吗?(即一定要等外贸的同事翻译完,我们工程师再去修改成能够用的形式吗?)

 

直击灵魂的三个问题!

 

王林森这文章的后半部分,将使用Singleton模式,使用简单易行的方法,解决这个问题。

=======================================================================

在开始我的方法之前,先看看现在都有哪些做法。

1 资源DLL。很显然,这需要编程能力。

2 .ini配置文件。这个网上大多的例子,在后期界面修改引入新的ID的时候,比较麻烦。

 

值得一提的是,微软在VS的示例里,给出了一个些多语言支持的例子。(我64位默认安装路径,好像在打SP1补丁包后,位于C:\Program Files (x86)\Microsoft Visual Studio 10.0\Samples\2052)

在VC2010Samples\C++\International里有三个例子。可以看下。

其中,

IME跟显示多语言支持无关。

unires用了String Table,调用LoadString来显示多种文字。写死了,不好改。

satdll这个Solusion有问题。

 

坑啊!

=======================================================================

好了,忘掉刚才黑暗的小路,王林森即将带领你走向光明的,呃,小道。

 

我们应当有一个解决方案,这个解决方案能够支持动态的多语言切换——这个不太重要,

                    支持语言集的拓展——这个重要,因为后期可能要加其他语言,

                    支持支持界面的和非界面的多语言文字——这个重要,因为除了界面,你可能还要生成报表,报表也得改啊。

顺便说下,多语言的本质是“根据用户的选择,提供不同的字符串”。

=======================================================================

配置文件分两种,

第一种,预览文件。在这个文件里,列出当前选用的是哪个语言,

                  一共有多少种语言,

                  每种语言的ID,

                  每种语言的名字,

                  每种语言的文件名。

可以按照这样的形式:

 1 [CurrentSetting]
 2 CurrentLanguageID=2
 3 
 4 [LanguageSum]
 5 Sum=2
 6 
 7 [Language1]
 8 LanguageID=1
 9 LanguageName=中文
10 LanguageFileName=Chinese.ini
11 
12 [Language2]
13 LanguageID=2
14 LanguageName=English
15 LanguageFileName=English.ini

 

配置文件的第二种,语言数据文件。

借鉴resource.h文件,王林森把每一个用到的字符串都分配一个字符串ID。

考虑到既有界面和控件是有资源ID的,

把资源ID和字符串ID都用

1 ID_MAIN_DLG_CAPTION    王林森的多语言支持示例程序
2 ID_STRING_HELLO        你好

的形式写在配置文件里。

这里注意一点。必须保证所有的ID_XXX_XXX不重名。

题外话,MFC程序里,不同类型控件的ID重名不影响,是因为.rc文件里指明了控件的类型。这里我们只是一个配置文件,为了简化并和谐统一,采用不重名的方式。

可以参照下面的形式:

中文

1 [LanguageText]
2 IDC_STATIC_PROVERB=当蝴蝶在南半球拍了两下翅膀,它就会稍微飞高一些。
3 IDC_BUTTON1=禁止转载
4 IDC_STATIC1=王林森
5 IDC_RADIO1=半瓶以上
6 IDC_RADIO2=一瓶未满

英文

1 [LanguageText]
2 IDC_STATIC_PROVERB=When the butterfly in the southern hemisphere beat the wings, it will fly a little higher.
3 IDC_BUTTON1=No reproduction without permission.
4 IDC_STATIC1=wlsandwho
5 IDC_RADIO1=Oooops
6 IDC_RADIO2=I can't translate this sentence into English.

 

那么问题来了,类如何知道在配置文件里有哪些ID的?

王林森在往配置文件写ID的时候,顺便用SetKey的方式,向一个容器注册一个ID名。

即在从无到有的创建一个语言数据文件时,

每写一行

1 ID_XXXXXX=XXX XXX

就在VS里写一行代码

1 m_oML.SetKey(TEXT("ID_XXXXXX"));

 

当然可以读取每一个配置文件,用解析文本文件的每一行的方式,得到“=”左边的键的文本,然后添加到一个set里,代替手工注册。这样就更简单了。

在王林森的示例代码中,会综合使用这两种方法。

=======================================================================

读写配置文件的函数参见MSDN。函数有很多。我只用三个。

GetPrivateProfileInt

GetPrivateProfileString

WritePrivateProfileString

 

前两个读数字和字符串,最后一个写字符数据。

 

现在要做的就是,读取预览文件,然后根据预览文件数据去读取语言数据文件。

是的,在一次初始化中,读取所有的配置文件。反正初始化也要时间,再多这半秒也无所谓。这方便后面动态切换语言。

=======================================================================

实际上,王林森使用了map生成了两个字典。

第一个是预览字典,存放的是预览文件的数据。

第二个是语言数据字典,存放的是众多的语言数据文件的数据。

=======================================================================

这样通过

  使用ID的文本作为Key查找,得到目标文本

  在界面处使用操作ID的API来使用文本/在非界面处,直接使用文本

的方式完成了多语言支持。

=======================================================================

看一下效果

切换前

切换后

=======================================================================

部分代码。

头文件:

  1 #pragma once
  2 //wlsandwho
  3 
  4 #include <atlstr.h>
  5 #include <vector>
  6 #include <map>
  7 #include <set>
  8 class CMultiLanguage
  9 {
 10 //////////////////////////////////////////////////////////////////////////
 11 public:
 12     typedef struct _LanguagePreviewData
 13     {
 14         int nID;
 15         CString strName;
 16         CString strFileName;
 17 
 18         bool operator <(const _LanguagePreviewData &oDR) const
 19         {
 20             return nID<oDR.nID;
 21         }
 22 
 23         _LanguagePreviewData& operator = (const _LanguagePreviewData & oDR)
 24         {
 25             nID=oDR.nID;
 26             strName=oDR.strName;
 27             strFileName=oDR.strFileName;
 28 
 29             return *this;
 30         }
 31 
 32     }LanguagePreviewData,*PLanguagePreviewData;
 33 
 34     typedef std::vector<LanguagePreviewData> VctLanguagePreview;
 35 
 36     typedef std::vector<CString> VctStrKey;
 37 
 38     typedef std::map<CString,CString> MapStrId2Text;
 39     typedef std::map<CString,CString>::iterator ItMapStrId2Text;
 40 
 41     typedef std::map<int,MapStrId2Text> MapLanguageId2Text;
 42     typedef std::map<int,MapStrId2Text>::iterator ItMapLanguageId2Text;
 43     typedef std::set<CString> SetKeys;
 44 
 45 //////////////////////////////////////////////////////////////////////////
 46 public:    
 47     void RegisterIDasKey(CString strKey);
 48     bool InitialMultiLanguageSupport(CString strPath);
 49 
 50     int GetCurrentLanguageID();
 51     bool ChangeLanguage(int nLanguageID);
 52 
 53     CString GetStringbyID( CString strStrID );
 54 
 55     VctLanguagePreview GetLanguagePreview();
 56     bool IsOnSupport();
 57 
 58 protected:
 59     CString GetFilePath();
 60     bool AutoRegisterIDasKey(CString strFileName);
 61 
 62     bool ReadLanguagePreview(CString strFileName);
 63     bool ReadLanguageData();
 64 
 65 protected:
 66     SetKeys m_setKeys;
 67     VctStrKey m_vctStrKey;
 68 
 69     bool m_bSupport;
 70     int m_nCurrentLanguageID;
 71 
 72     CString m_strFileName;
 73 
 74     VctLanguagePreview m_vctLanguagePreview;
 75     MapLanguageId2Text m_mapLanguageId2Text;
 76 
 77 //////////////////////////////////////////////////////////////////////////
 78 public:
 79     ~CMultiLanguage(void);
 80 
 81 protected:
 82     CMultiLanguage(void);
 83     CMultiLanguage(const CMultiLanguage&);
 84      CMultiLanguage& operator=(const CMultiLanguage&);
 85 
 86 //////////////////////////////////////////////////////////////////////////
 87 public:
 88     static CMultiLanguage* GetInstance();
 89 
 90 protected:
 91     static CMultiLanguage* m_pInstance;
 92 
 93 //////////////////////////////////////////////////////////////////////////
 94 private:
 95     class CAutoDeleter
 96     {
 97     public:
 98         ~CAutoDeleter()
 99         {
100             if (CMultiLanguage::m_pInstance)
101             {
102                 delete CMultiLanguage::m_pInstance;
103                 CMultiLanguage::m_pInstance=NULL;
104             }
105         }
106     };
107 //////////////////////////////////////////////////////////////////////////
108 private:
109     typedef CCriticalSection CLock;
110 //////////////////////////////////////////////////////////////////////////
111 private:
112     static CLock m_oLock;
113     static CAutoDeleter m_oAutoDeleter;
114 };

源文件:

  1 #include "StdAfx.h"
  2 
  3 #include "Multilanguage.h"
  4 #include "afx.h"
  5 
  6 CMultiLanguage::CMultiLanguage(void)
  7 {
  8     m_nCurrentLanguageID=1;
  9     m_bSupport=false;
 10 
 11     m_strFileName=TEXT("");
 12 
 13     m_vctLanguagePreview.clear();
 14     m_setKeys.clear();
 15     m_vctStrKey.clear();
 16     m_mapLanguageId2Text.clear();
 17 }
 18 
 19 CMultiLanguage::~CMultiLanguage(void)
 20 {
 21     m_vctLanguagePreview.clear();
 22     m_setKeys.clear();
 23     m_vctStrKey.clear();
 24     m_mapLanguageId2Text.clear();
 25 }
 26 
 27 CString CMultiLanguage::GetStringbyID( CString strStrID )
 28 {
 29     CString strRes=TEXT("wlsandwho");
 30 
 31     ItMapLanguageId2Text itLId2Text=m_mapLanguageId2Text.find(m_nCurrentLanguageID);
 32     if (itLId2Text!=m_mapLanguageId2Text.end())
 33     {
 34         ItMapStrId2Text itStrId2Text=itLId2Text->second.find(strStrID);
 35         if (itStrId2Text!=itLId2Text->second.end())
 36         {
 37             strRes=itStrId2Text->second;
 38         }
 39     }
 40 
 41     return strRes;
 42 }
 43 
 44 bool CMultiLanguage::ChangeLanguage( int nLanguageID )
 45 {
 46     m_oLock.Lock();
 47     //Change the current language text.
 48     m_nCurrentLanguageID=nLanguageID;
 49 
 50     //Save the configure.
 51     CString strCurrentLanguageID;
 52     strCurrentLanguageID.Format(TEXT("%d"),m_nCurrentLanguageID);
 53 
 54     WritePrivateProfileString(TEXT("CurrentSetting"),TEXT("CurrentLanguageID"),strCurrentLanguageID,m_strFileName);
 55 
 56     m_oLock.Unlock();
 57 
 58     return true;
 59 }
 60 
 61 bool CMultiLanguage::InitialMultiLanguageSupport(CString strFileName)
 62 {
 63     bool bMade = true;
 64 
 65     m_oLock.Lock();
 66     //bMade = ReadLanguagePreview(strFileName) ? AutoRegisterIDasKey(m_vctLanguagePreview[0].strFileName) && ReadLanguageData() && (m_bSupport = true) : m_bSupport = false;
 67 
 68     if (ReadLanguagePreview(strFileName))
 69     {
 70         int nFilesNumber = m_vctLanguagePreview.size();
 71 
 72         for (int i = 0; i < nFilesNumber; i++)
 73         {
 74             if (!AutoRegisterIDasKey(m_vctLanguagePreview[i].strFileName))
 75             {
 76                 bMade = false;
 77                 break;
 78             }
 79         }
 80 
 81         bMade=ReadLanguageData();
 82     }
 83     else
 84     {
 85         bMade = false;
 86     }
 87     
 88     m_bSupport = bMade;
 89 
 90     m_oLock.Unlock();
 91 
 92     return bMade;
 93 }
 94 
 95 bool CMultiLanguage::ReadLanguagePreview( CString strFileName )
 96 {
 97     bool bOK=true;
 98     
 99     m_nCurrentLanguageID=GetPrivateProfileInt(TEXT("CurrentSetting"),TEXT("CurrentLanguageID"),0,strFileName);
100     if (m_nCurrentLanguageID==0)
101     {
102         bOK=false;
103     }
104 
105     m_strFileName=strFileName;
106 
107     int nLanguageSum=0;
108     nLanguageSum=GetPrivateProfileInt(TEXT("LanguageSum"),TEXT("Sum"),0,strFileName);
109 
110     CString strLanguageNo=TEXT("Language");
111     CString strTempLanguageNo;
112 
113     TCHAR szReturnString[MAX_PATH];
114     
115     for (int i=1;i<=nLanguageSum;i++)
116     {
117         strTempLanguageNo.Format(TEXT("%d"),i);
118         strTempLanguageNo=strLanguageNo+strTempLanguageNo;
119 
120         LanguagePreviewData stLPD;
121         stLPD.nID=GetPrivateProfileInt(strTempLanguageNo,TEXT("LanguageID"),0,strFileName);
122 
123         memset(szReturnString,0,MAX_PATH);
124         GetPrivateProfileString(strTempLanguageNo,TEXT("LanguageName"),TEXT("wlsandwho"),szReturnString,MAX_PATH,strFileName);
125         stLPD.strName=CString(szReturnString);
126 
127         memset(szReturnString,0,MAX_PATH);
128         GetPrivateProfileString(strTempLanguageNo,TEXT("LanguageFileName"),TEXT("wlsandwho"),szReturnString,MAX_PATH,strFileName);
129         stLPD.strFileName=CString(szReturnString);
130 
131         m_vctLanguagePreview.push_back(stLPD);
132     }
133 
134     if (m_vctLanguagePreview.size()<1)
135     {
136         bOK = false;
137     }
138 
139     return bOK;
140 }
141 
142 CString CMultiLanguage::GetFilePath()
143 {
144     TCHAR szPath[MAX_PATH];
145     GetCurrentDirectory(MAX_PATH, szPath);
146     CString strPath = szPath + CString(TEXT("\\"));
147 
148     return    strPath;
149 }
150 
151 bool CMultiLanguage::ReadLanguageData()
152 {
153     int nSize=m_vctLanguagePreview.size();
154     if (nSize<1)
155     {
156         return false;
157     }
158 
159     TCHAR szReturnString[MAX_PATH];
160 
161     CString strPath=GetFilePath();
162 
163     for (int i=0;i<nSize;i++)
164     {
165         m_vctLanguagePreview[i].strFileName;
166         
167         memset(szReturnString,0,MAX_PATH);
168 
169         int nKeySize=m_vctStrKey.size();
170 
171         MapStrId2Text mapTempStrId2Text;
172         for (int j=0;j<nKeySize;j++)
173         {
174             GetPrivateProfileString(TEXT("LanguageText"),m_vctStrKey[j],TEXT("wlsandwho"),szReturnString,MAX_PATH,strPath+m_vctLanguagePreview[i].strFileName);
175             mapTempStrId2Text.insert(std::make_pair(m_vctStrKey[j],CString(szReturnString)));
176         }
177 
178         m_mapLanguageId2Text.insert(std::make_pair(m_vctLanguagePreview[i].nID,mapTempStrId2Text));
179     }
180 
181     return true;
182 }
183 
184 bool CMultiLanguage::AutoRegisterIDasKey(CString strFileName)
185 {
186     CStdioFile oStdFile;
187 
188     int nPos = -1;
189     CString strTemp;
190     BOOL bFile=oStdFile.Open(GetFilePath()+TEXT("\\")+strFileName, CFile::modeRead);
191     if (bFile==FALSE)
192     {
193         return false;
194     }
195 
196     while (oStdFile.ReadString(strTemp))
197     {
198         nPos=strTemp.Find(TEXT("="));
199         if (nPos!=-1)
200         {
201             strTemp=strTemp.Left(nPos);
202             m_setKeys.insert(strTemp);
203         }
204     }
205 
206     oStdFile.Close();
207     
208     VctStrKey vctTempStrKey(m_setKeys.begin(), m_setKeys.end());
209     
210     m_vctStrKey = vctTempStrKey;
211 
212     return true;
213 }
214 
215 void CMultiLanguage::RegisterIDasKey( CString strKey )
216 {
217     m_vctStrKey.push_back(strKey);
218 }
219 
220 int CMultiLanguage::GetCurrentLanguageID()
221 {
222     return m_nCurrentLanguageID;
223 }
224 
225 CMultiLanguage::VctLanguagePreview CMultiLanguage::GetLanguagePreview()
226 {
227     return m_vctLanguagePreview;
228 }
229 
230 //////////////////////////////////////////////////////////////////////////
231 
232 CMultiLanguage* CMultiLanguage::GetInstance()
233 {
234     if (m_pInstance==NULL)
235     {
236         m_oLock.Lock();
237         if (m_pInstance==NULL)
238         {
239             m_pInstance=new CMultiLanguage;
240         }
241         m_oLock.Unlock();
242     }
243 
244     return m_pInstance;
245 }
246 
247 bool CMultiLanguage::IsOnSupport()
248 {
249     return m_bSupport;
250 }
251 
252 CMultiLanguage* CMultiLanguage::m_pInstance=NULL;
253 
254 CMultiLanguage::CLock CMultiLanguage::m_oLock;
255 
256 CMultiLanguage::CAutoDeleter CMultiLanguage::m_oAutoDeleter;

VS2015源码下载链接:TestMultiLanguage-wlsandwho.zip

=======================================================================

(友情支持请扫描这个)

微信扫描上方二维码捐赠