A c ++ programming question that tests your design capabilities

Source: Internet
Author: User


After seeing this question, we began to design this image class. According to the object-oriented "Dependency inversion" design principle, we decided on the customer's standpoint to consider what interfaces should we provide for this class, soon we designed the next class
Class CSimplePicture
{
Public:
CSimplePicture (char * init [], int nCount );
CSimplePicture (CSimplePicture & p1, CSimplePicture & p2, bool bVerCat );

Void Frame ();
Void Print (std: ostream & OS) const;
Protected:
Std: vector <std: string> m_arData;
};

CSimplePicture (char * init [], int nCount );
Construct an image based on the string array.

CSimplePicture (CSimplePicture & p1, CSimplePicture & p2, bool bVerCat );
Construct an image based on the two images. bVerCat indicates whether it is vertical or horizontal join.

Void Frame ();
Frame image objects

Void Print (std: ostream & OS) const;
Print Output Image

Std: vector <std: string> m_arData;
String Array for storing image data

The specific implementation will be considered below. This is very easy for people with certain development experience and will not be detailed,
CSimplePicture (char * init [], int nCount) is nothing more than data copying. CSimplePicture (CSimplePicture & p1, CSimplePicture & p2, bool bVerCat) connects the data of the two images, together, the void Frame () modifies the data in it with a border, and the void Print (std: ostream & OS) const traverses the string array output.

According to the above design and implementation, we should have met the requirements of this question.
However, the customer's requirements are changeable. Now, the customer has another new requirement, requiring that a picture be removed from the border.
In addition, customers think that the performance of our image class is too poor, and each frame or merged image requires a large amount of memory copying.

At this moment, we are dumpfounded. The damn customers, according to our above design, do not support these new features at all, because we store the internal string data of the image, I don't know if it has been framed. In addition, our image data itself does not support sharing.

Next, we will re-consider the design. The key to how to make our image objects support the UnFrame operation is to establish the level of our image type, in this way, you can determine whether it is a framed class object, so you have the following class layers:
// Image interface base class
Class CPic_Base
{};

// String image class
Class CPic_String: public CPic_Base
{};

// Framed Image
Class CPic_Frame: public CPic_Base
{}

// Vertically connected image class
Class CPic_VCat: public CPic_Base
{};

// Horizontal join Image
Class CPic_HCat: public CPic_Base
{};

Then we will consider how to share image data. This requires smart pointers. Generally, there are two implementations of smart pointers in C ++, one is auto_ptr in STL, another type is reference-based counting. The essence of auto_ptr is to have a relationship, that is, after you have this object, others cannot have it, so it does not meet our requirements. Reference counting is a good thing and especially useful for shared objects. The IUnknow interface in COM is based on this technology, and variables are automatically destroyed in many scripting languages, in fact, it is based on reference counting technology. Here we will share a smart pointer class based on reference count.
Class CRefCountBase
{
Public:
CRefCountBase ()
{
M_nRefCount = 0;
}

Int GetRefCount () const
{
Return m_nRefCount;
}

Int AddRefCount ()
{
Return ++ m_nRefCount;
}

Int SubRefCount ()
{
Return -- m_nRefCount;
}

Void ResetRefCount ()
{
M_nRefCount = 0;
}

Private:
Int m_nRefCount;
};

Template <typename T>
Class CRefPtr
{
Public:
T * operator-> () const
{
Return m_pRawObj;
}

T & operator () const
{
Return * m_pRawObj;
}

T & operator * () const
{
Return * m_pRawObj;
}

T * GetPtr () const
{
Return m_pRawObj;
}

Bool IsNull () const
{
Return m_pRawObj = NULL;
}

CRefPtr ()
{
M_pRawObj = NULL;
}

CRefPtr (T * p)
{
M_pRawObj = p;
If (p! = NULL)
{
P-> AddRefCount ();
}
}

CRefPtr (const CRefPtr & ref)
{
M_pRawObj = ref. m_pRawObj;
If (m_pRawObj! = NULL)
{
M_pRawObj-> AddRefCount ();
}
}

~ CRefPtr ()
{
If (m_pRawObj! = NULL & m_pRawObj-> SubRefCount () = 0)
{
Delete m_pRawObj;
}
}

CRefPtr & operator = (const CRefPtr & ref)
{
If (this! = & Ref)
{
If (m_pRawObj! = NULL
& M_pRawObj-> SubRefCount () = 0)
{
Delete m_pRawObj;
}

M_pRawObj = ref. m_pRawObj;

If (m_pRawObj! = NULL)
{
M_pRawObj-> AddRefCount ();
}
}

Return * this;
}

Bool operator = (const CRefPtr & ref) const
{
Return m_pRawObj = ref. m_pRawObj;
}

CRefPtr <T> Copy ()
{
If (m_pRawObj! = NULL)
{
T * p = new T (* m_pRawObj );
P-> ResetRefCount ();

Return p;
}
Else
{
Return NULL;
}
}

Private:
T * m_pRawObj;
};

Use this class in this way
Class A: public CRefCountBase
{
Public:
Void fun1 ();
};

CRefPtr <A> p = new;
P-> fun1 ();

Redesign our CPic_Base,
Class CPic_Base: public CRefCountBase
{
Public:
Virtual ~ CPic_Base (){}

// Print the output image
Void Print (std: ostream & OS) const;

// Return Image Width
Virtual int GetWidth () const = 0;

// Return the Image Height
Virtual int GetHeight () const = 0;

// Returns the image string data of a row.
Virtual std: string GetLineData (int nLineIndex) const = 0;

// Return the object for removing the border
Virtual CRefPtr <CPic_Base> GetUnFrame () const {return NULL ;}
};

The Print method is easy to implement here:
Void CPic_Base: Print (std: ostream & OS) const
{
For (int I = 0; I <GetHeight (); ++ I)
{
OS <GetLineData (I );
OS <"\ n ";
}
}


Then consider implementing CPic_String
Class CPic_String: public CPic_Base
{
Public:
CPic_String (char * p [], int nCount );

Virtual int GetWidth () const;
Virtual int GetHeight () const;
Virtual std: string GetLineData (int nLineIndex) const;


Protected:
Std: vector <std: string> m_arData;
};
This class stores the real string image data, and the implementation of the method in it is also very simple. Similar to the first implementation, it will not be detailed.


CPic_Frame
Class CPic_Frame: public CPic_Base
{
Public:
CPic_Frame (CRefPtr <CPic_Base> & pic );

Virtual int GetWidth () const;
Virtual int GetHeight () const;
Virtual std: string GetLineData (int nLineIndex) const;

Virtual CRefPtr <CPic_Base> GetUnFrame () const {return m_pic ;}

Protected:
CRefPtr <CPic_Base> m_pic;
};
We can see that here we reference another image data, instead of actually storing the data, the method implementation is also very simple, mainly dependent on the image class pointed to by m_pic, at the same time, m_pic is a smart pointer based on reference count, so there is no memory copy when assigning values. Note that the GetUnFrame method only returns non-NULL here, indicating that only such objects support border removal.
CPic_Frame: CPic_Frame (CRefPtr <CPic_Base> & pic)
: M_pic (pic)
{
_ ASSERTE (! M_pic.IsNull ());
}

Int CPic_Frame: GetWidth () const
{
Return m_pic-> GetWidth () + 2;
}

Int CPic_Frame: GetHeight () const
{
Return m_pic-> GetHeight () + 2;
}

String CPic_Frame: GetLineData (int nLineIndex) const
{
Int nWidth = GetWidth ();
Int nHeight = GetHeight ();

_ ASSERTE (nLineIndex <nHeight & nLineIndex> = 0 );

If (nLineIndex = 0 // first line and last line
| NLineIndex = nHeight-1)
{
Int nPadding = nWidth-2;
Return string ("+") + string (nPadding, '-') + string ("+ ");
}
Else
{
Return string ("|") + m_pic-> GetLineData (nLineIndex-1) + string ("| ");
}
}

CPic_VCat
Class CPic_VCat: public CPic_Base
{
Public:
CPic_VCat (CRefPtr <CPic_Base> & pic1, CRefPtr <CPic_Base> & pic2 );

Virtual int GetWidth () const;
Virtual int GetHeight () const;
Virtual std: string GetLineData (int nLineIndex) const;

Protected:
CRefPtr <CPic_Base> m_pic1;
CRefPtr <CPic_Base> m_pic2;
};
It stores up and down two image objects, and the implementation of the method is not complicated, so it is not detailed.

In addition, CPic_HCat is similar:
Class CPic_HCat: public CPic_Base
{
Public:
CPic_HCat (CRefPtr <CPic_Base> & pic1, CRefPtr <CPic_Base> & pic2 );

Virtual int GetWidth () const;
Virtual int GetHeight () const;
Virtual std: string GetLineData (int nLineIndex) const;

Protected:
CRefPtr <CPic_Base> m_pic1;
CRefPtr <CPic_Base> m_pic2;
};

With the above implementation, we can now implement the functions we need:
Int main ()
{
Char * init1 [] = {"Paris", "in the", "Spring "};
CRefPtr <CPic_Base> p1 = new CPic_String (init, 3 );

CRefPtr <CPic_Base> p2 = new CPic_Frame (p1 );

CRefPtr <CPic_Base> p3 = new CPic_VCat (p1, p2 );

P3-> Print (cout );
CRefPtr <CPic_Base> p4 = p2-> GetUnFrame ();
}

At this time, we found that this is unfriendly to customer calls, because the class layers we implement internally are exposed to the customer, and the information should be transparent to the customer, we should encapsulate a simpler interface class for the customer.

So we have the following design. In fact, the interface is similar to our first implementation.
Class CPicture
{
Public:
CPicture (char * p [], int nCount );
CPicture (CPicture & p1, CPicture & p2, bool bVerCat );

Void Frame ();
Bool UnFrame ();

Friend std: ostream & operator <(std: ostream & OS, const CPicture & pic );

Protected:
CRefPtr <CPic_Base> m_pic;
};

Std: ostream & operator <(std: ostream & OS, const CPicture & pic );

In this way, customers only need to deal with CPicture and do not have to worry about internal implementation.
The implementation of this class is also very simple:
CPicture: CPicture (char * p [], int nCount)
{
M_pic = new CPic_String (p, nCount );
}

CPicture: CPicture (CPicture & pic1, CPicture & pic2, bool bVerCat)
{
If (! BVerCat)
{
M_pic = new CPic_HCat (pic1.m _ pic, pic2.m _ pic );
}
Else
{
M_pic = new CPic_VCat (pic1.m _ pic, pic2.m _ pic );
}
}

Void CPicture: Frame ()
{
M_pic = new CPic_Frame (m_pic );
}

Bool CPicture: UnFrame ()
{
CRefPtr <CPic_Base> p = m_pic-> GetUnFrame ();
If (! P. IsNull ())
{
M_pic = p;
}

Return! P. IsNull ();
}

Std: ostream & operator <(std: ostream & OS, const CPicture & pic)
{
Pic. m_pic-> Print (OS );
Return OS;
}

The following code uses this class:
Char * init1 [] = {"Paris", "in the", "Spring "};
Char * init2 [] = {"Hello world", "every", "thing", "is", "OK! "};

Int main (int argc, char * argv [])
{
CPicture p1 (init1, 3 );
CPicture p2 (init2, 5 );

//
Std: cout <p1;
Cout <endl;

//
Std: cout <p2;
Cout <endl;

//
P2.Frame ();
Cout <p2;
Cout <endl;

//
P1.Frame ();
P1.Frame ();
Cout <p1;
Cout <endl;

//
CPicture pHorCat (p1, p2, false );
Cout <pHorCat;
Cout <endl;

//
CPicture pVerCat (p1, pHorCat, true );
Cout <pVerCat;
Cout <endl;

//
PVerCat. Frame ();
Cout <pVerCat;
Cout <endl;

//
PVerCat. Frame ();
Cout <pVerCat;
Cout <endl;

//
PVerCat. UnFrame ();
PVerCat. UnFrame ();
Cout <pVerCat;
Cout <endl;

System ("pause ");

Return 0;
}

We can see that it is very convenient and friendly to use and runs:

 

We can see that using the second implementation, we only store a string of image data, while retaining the level and structure attributes of the image, the Implementation contains many design patterns, such as Template, Decorate, composite and faced are simple and efficient.


Finally, we will compare the two implementation methods:
The advantage of method 1 is data integrity. Modifying an object does not affect other objects, because each object is a separate copy of data. The disadvantage is inefficiency, which does not reflect the structural properties of the object. We do not know whether the object is a border object or an up or down merged object.

The advantage of method 2 is high efficiency, data sharing, and retaining the structure attributes of objects. The disadvantage is that modifying an object affects other objects because they may share the same object. In fact, for shared objects based on reference count, there is also a technology called Write Copy (Copy at Write time), that is, if you want to modify an object, Copy it yourself. At the same time, the risk of referencing the counting technology is circular reference. For example, if A references B and B also references A, these two objects will never be released, so be cautious.

The above perfectly solved our UnFrame (de-border) problem. We are proud to use the reference counting technology to perfectly construct the class hierarchy of string images, but it is not a long time.

A week later, the customer finds out that you have raised his new requirements. He wants to add a function to your CPicuture class, returns an XML string to tell the object construction process.
For example
+ ------- +
| Paris |
| In the |
| Spring |
+ ------- +
The returned XML string is
<CPic_Frame>
<CPic_String> Paris in the Spring </CPic_String>
</CPic_Frame>

+ ------- + Paris
| Paris | in
| In the | Spring
| Spring |
+ ------- +
The returned XML string is
<CPic_HCat>
<CPic_Frame>
<CPic_String> Paris in the Spring </CPic_String>
</CPic_Frame>
<CPic_String> Paris in the Spring </CPic_String>
</CPic_HCat>

+ ------- + Paris
| Paris | in
| In the | Spring
| Spring |
+ ------- +
Paris
In
Spring
The returned XML string is
<CPic_VCat>
<CPic_HCat>
<CPic_Frame>
<CPic_String> Paris in the Spring </CPic_String>
</CPic_Frame>
<CPic_String> Paris in the Spring </CPic_String>
</CPic_HCat>
<CPic_String> Paris in the Spring </CPic_String>
</CPic_VCat>

You can't help but complain that, the last time the UnFrame feature was supported, the original design was changed for me. If there were no new demands from customers, development is a wonderful thing.

But the complaints are complaints. The customer is God, and you can only finish the work.
Now let's consider how to implement this function.

The first thought was to add an interface to our CPic_Base base class, for example
String GetStructXMLString ();
However, the image-oriented design principle tells us that the interface should not be changed at will. In fact, the CRefPtr <CPic_Base> GetUnFrame () interface added for UnFrame in CPic_Base has made you feel uncomfortable, we feel that this interface is not directly related to our image objects.

So whether we can reconstruct the CPic_Base interface so that it can implement various functions in the form of plug-ins, that is, our class level is fixed here, however, methods can be added without affecting the original code.

At this time, we thought of the Visitor model, which is basically tailored to our needs.
The architecture of the Visitor mode is basically fixed and an IPic_Visitor is defined.
Class IPic_Visitor
{
Public:
Virtual void VisitPicString (CPic_String & pic ){};
Virtual void VisitPicFrame (CPic_Frame & pic ){};
Virtual void VisitPicVCat (CPic_VCat & pic ){};
Virtual void VisitPicHCat (CPic_HCat & pic ){};

Virtual ~ IPic_Visitor (){}
};


Add an Accept interface virtual void Accept (IPic_Visitor & visitor) = 0 in our CPic_Base base class;
In this way, the image object can be accessed by various types of visitors, and the implementation of each image class is also very simple:
Void CPic_String: Accept (IPic_Visitor & visitor)
{
Visitor. VisitPicString (* this );
}
Void CPic_Frame: Accept (IPic_Visitor & visitor)
{
Visitor. VisitPicFrame (* this );
}
Void CPic_VCat: Accept (IPic_Visitor & visitor)
{
Visitor. VisitPicVCat (* this );
}
Void CPic_HCat: Accept (IPic_Visitor & visitor)
{
Visitor. VisitPicHCat (* this );
}

Now, we use a new Visitor to rewrite our original UnFrame function,
Class CUnFrameVisitor: public IPic_Visitor
{
Public:
Virtual void VisitPicFrame (CPic_Frame & pic );

Public:
CRefPtr <CPic_Base> GetUnFrameResult ();

Protected:
CRefPtr <CPic_Base> m_picRet;
};
Because the Visitor method does not return values, and the parameters are fixed, they are generally returned by saving the member variables and return interfaces in the Visitor.
The implementation is simple:
Void CUnFrameVisitor: VisitPicFrame (CPic_Frame & pic)
{
M_picRet = pic. m_pic;
}

CRefPtr <CPic_Base> CUnFrameVisitor: GetUnFrameResult ()
{
Return m_picRet;
}
You can see that only CPic_Frame is accessed with a non-null return value. Others use the default null method, and the final returned empty object is also displayed.

In this way, it is easy to implement UnFrame in the final exposed CPicture:
Bool CPicture: UnFrame ()
{
CUnFrameVisitor vistor;
M_pic-> Accept (vistor );

CRefPtr <CPic_Base> pRet = vistor. GetUnFrameResult ();
If (! PRet. IsNull ())
{
M_pic = pRet;
}

Return! PRet. IsNull ();
}

Next we will consider how to meet the requirements of customers to return XML strings. In fact, we have prepared the conditions for the previous Visitor mode. We only need to add a new Visitor www.2cto.com
Class CStructXMLVisitor: public IPic_Visitor
{
Public:
Virtual void VisitPicString (CPic_String & pic );
Virtual void VisitPicFrame (CPic_Frame & pic );
Virtual void VisitPicVCat (CPic_VCat & pic );
Virtual void VisitPicHCat (CPic_HCat & pic );

Public:
Std: string GetStructXMLString () {return m_strStructXML ;}

Protected:
Std: string m_strStructXML;
};

Implementation is not complex:
Void CStructXMLVisitor: VisitPicString (CPic_String & pic)
{
M_strStructXML = "<CPic_String> ";
Int nHeight = pic. GetHeight ();
For (int I = 0; I <nHeight; ++ I)
{
M_strStructXML + = pic. GetLineData (I );
}
M_strStructXML + = "</CPic_String> ";
}

Void CStructXMLVisitor: VisitPicFrame (CPic_Frame & pic)
{
CStructXMLVisitor v;
Pic. m_pic-> Accept (v );
M_strStructXML = "<CPic_Frame> ";
M_strStructXML + = v. GetStructXMLString ();
M_strStructXML + = "</CPic_Frame> ";
}

Void CStructXMLVisitor: VisitPicVCat (CPic_VCat & pic)
{
M_strStructXML = "<CPic_VCat> ";
CStructXMLVisitor v1;
Pic. m_pic1-> Accept (v1 );
M_strStructXML + = v1.GetStructXMLString ();

CStructXMLVisitor v2;
Pic. m_pic2-> Accept (v2 );
M_strStructXML + = v2.GetStructXMLString ();

M_strStructXML + = "</CPic_VCat> ";
}

Void CStructXMLVisitor: VisitPicHCat (CPic_HCat & pic)
{
M_strStructXML = "<CPic_HCat> ";
CStructXMLVisitor v1;
Pic. m_pic1-> Accept (v1 );
M_strStructXML + = v1.GetStructXMLString ();

CStructXMLVisitor v2;
Pic. m_pic2-> Accept (v2 );
M_strStructXML + = v2.GetStructXMLString ();

M_strStructXML + = "</CPic_HCat> ";
}

Then we add a GetStructXMLString method in our CPicture interface, and the implementation is very simple:
Std: string CPicture: GetStructXMLString ()
{
CStructXMLVisitor v;
M_pic-> Accept (v );
Return v. GetStructXMLString ();
}

We can see that, after switching to a new design, we will have any new requirements in the future. We only need to add a Visitor directly. Therefore, the design is not a constant layer, and we need to rebuild it continuously based on the needs.
Finally, paste the class diagram. external users only need to deal with CPicture:

 


Source code download: http://www.bkjia.com/uploadfile/2012/0619/20120619094552671.rar
Http://www.bkjia.com/uploadfile/2012/0619/20120619094553222.rar
By Richard Wei

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.