Is it normal when a base class has only one derived class?

I am creating a password module using OOD and design patterns. The module will store a log of recorded events and read / write to a file. I created an interface in a base class and an implementation in a derived class. Now I am wondering if this is some bad smell, if the base class has only one derived class. Is this class hierarchy needed? Now, to eliminate the class hierarchy, I can, of course, just do everything in one class and not output at all, here is my code.

class CLogFile { public: CLogFile(void); virtual ~CLogFile(void); virtual void Read(CString strLog) = 0; virtual void Write(CString strNewMsg) = 0; }; 

Derived class:

 class CLogFileImpl : public CLogFile { public: CLogFileImpl(CString strLogFileName, CString & strLog); virtual ~CLogFileImpl(void); virtual void Read(CString strLog); virtual void Write(CString strNewMsg); protected: CString & m_strLog; // the log file data CString m_strLogFileName; // file name }; 

Now in code

 CLogFile * m_LogFile = new CLogFileImpl( m_strLogPath, m_strLog ); m_LogFile->Write("Log file created"); 

My question is that I follow the principles of OOD and first create an interface and implement it in a derived class. On the other hand, is it overkill, and does this complicate the situation? My code is simple enough not to use any design patterns, but it gets the keys to it in terms of encapsulating shared data through a derived class.

Ultimately, is the above class hierarchy good or should it be made in the same class instead?

+4
source share
6 answers

No, actually I think your design is good. Subsequently, you may need to add a layout or test implementation for your class, and your design makes it easier.

+7
source

The answer depends on how likely it is that you will have more than one behavior for this interface.

Read and write operations for the file system may make sense now. What if you decide to write something remote, like a database? In this case, the new implementation still works fine without affecting the clients.

I would say that this is a great example of how to make an interface.

Shouldn't you make the destructor pure virtual? If I remember correctly, the recommended idiom for creating a C ++ interface is according to Scott Myers.

+4
source

Yes, this is acceptable even when using only one interface, but can be slower at runtime (a bit) than a single class. ( virtual sending has roughly the cost of the following 1-2 function pointers)

This can be used as a way to prevent client dependencies on implementation details. For example, the clients of your interface do not need to be recompiled just because your implementation receives a new data field under your template.

You can also look at the pImpl template, which is a way to hide implementation details without using inheritance.

+1
source

No. If there is no polymorphism in action, there is no reason for inheritance, and you should use the refactoring rule to put two classes in one. "Prefer composition over inheritance."

Edit: as @crush commented, “prefer composition over inheritance” may not be an adequate quote. So say: if you think you need to use inheritance, think twice. And if you are really sure that you need to use it, think about it again.

0
source

Your model works well with the factory model, where you work with a lot of common pointers, and you call the factory method to “get” a common pointer to the abstract interface.

The downside of using pImpl is that it controls the pointer itself. With C ++ 11, however, pImpl will work well with the ability to move, so it will be more efficient. Currently, if you want to return an instance of your class from the "factory" function, it has semantic copy problems with an internal pointer.

This causes developers to return a generic pointer to an external class that is not copied. This means that you have a common pointer to one class containing a pointer to an inner class, so function calls go through this additional level of indirection, and you get two "new" ones in one construct. If you have only a small number of these objects that are not a serious problem, but this can be a little awkward.

C ++ 11 has the advantage of having a unique_ptr that supports the direct declaration of its basic and moving semantics. This way pImpl will become more doable if you really know that you will have only one implementation.

By the way, I would get rid of these CString and replace them with std::string , and not put C as a prefix for each class. I would also make implementation data members private, not secure.

0
source

An alternative model that you could use, as defined by Composition over Inheritance and the Principle of One Responsibility referred to by Stefan Rolland, has implemented the following model.

First you need three different classes:

 class CLog { CLogReader* m_Reader; CLogWriter* m_Writer; public: void Read(CString& strLog) { m_Reader->Read(strLog); } void Write(const CString& strNewMsg) { m_Writer->Write(strNewMsg); } void setReader(CLogReader* reader) { m_Reader = reader; } void setWriter(CLogWriter* writer) { m_Writer = writer; } }; 

CLogReader handles the sole responsibility of reading logs:

 class CLogReader { public: virtual void Read(CString& strLog) { //read to the string. } }; 

CLogWriter handles the sole responsibility for logging:

 class CLogWriter { public: virtual void Write(const CString& strNewMsg) { //Write the string; } }; 

Then, if you want your CLog to, say, write to the socket, you get CLogWriter:

 class CLogSocketWriter : public CLogWriter { public: void Write(const CString& strNewMsg) { //Write to socket? } }; 

And then install your instance CLog instance into the CLogSocketWriter instance:

 CLog* log = new CLog(); log->setWriter(new CLogSocketWriter()); log->Write("Write something to a socket"); 

Pros The advantage of this method is that you follow the principle of shared responsibility, since each class has one purpose. This gives you the opportunity to expand one goal without dragging code that you will not modify in any way. It also allows you to change components as you like without having to create an entire new CLog class for this purpose. For example, you might have a Writer that writes to a socket, but a reader reading a local file. Etc.

Against memory management is becoming a huge problem for us. You must keep track of when to remove pointers. In this case, you need to delete them when you destroy the CLog, as well as when setting up another Writer or Reader. Doing this if links are stored elsewhere can lead to dangling pointers. This would be a great opportunity to learn about strong and weak links, which are containers of link counts that automatically delete their pointer when all links to it are lost.

0
source

All Articles