- Back to Home »
- Hide Implementation Details:
Posted by : Sushanth
Thursday, 17 December 2015
Hide Implementation Details:
The piece of code which is most likely to change should be hided from the client so that the changes at a later date doesn’t affect existing customers.
The piece of code which is most likely to change should be hided from the client so that the changes at a later date doesn’t affect existing customers.
There are 2 categories in hiding implementation details:
1. Physical hiding:
The purpose is to hide the private source code from the users.In C++,this can be achived by providing the declarations in .h files and the definitions in .cpp files.
A declaration simply introduces a name, and its type, to the compiler without allocating any memory for it. In contrast, a definition provides details of a type's structure or allocates memory in the case of variables.
Example: Identify the encapsulation issue in below program
class MyClass
{
public:
void MyMethod()
{
printf("In MyMethod() of MyClass.\n");
}
};
{
public:
void MyMethod()
{
printf("In MyMethod() of MyClass.\n");
}
};
Answer:
The above program implicitly requests the compiler to inline the MyMethod() function at all points where it is called.
Note: Inlining the code into client’s program is a bad practice.
“Physical hiding means storing internal details in a separate file (.cpp) from the public interface (.h). “
2.Logical Hiding:
Its popularly known as the encapsulation. Its a mechanism for limiting access to members of an object. In C++ this is implemented using the following access control keywords
• Public: Members are accessible outside of the class/struct. This is the default access level for structs.
• Protected: Members are accessible within the specific class and any derived classes only.
• Private: Members are accessible only within the specific class they are defined within. This is the default access level for classes.
Its popularly known as the encapsulation. Its a mechanism for limiting access to members of an object. In C++ this is implemented using the following access control keywords
• Public: Members are accessible outside of the class/struct. This is the default access level for structs.
• Protected: Members are accessible within the specific class and any derived classes only.
• Private: Members are accessible only within the specific class they are defined within. This is the default access level for classes.
Hide member variables:
Provide indirect access to the members of the class using getter() and setter() methods
class Vector3
{
public:
double GetX() const;
double GetY() const;
void SetX(double val);
void SetY(double val);
Provide indirect access to the members of the class using getter() and setter() methods
class Vector3
{
public:
double GetX() const;
double GetY() const;
void SetX(double val);
void SetY(double val);
private:
double mX, mY;
};
double mX, mY;
};
Benefits of hiding member variables:
Validation: Validations can be performed on the values to ensure that the internal state of the class is always valid and consistent.
Finer access control: By using getter/setter methods, a finer level of read/ write control can be provided. For example, you can make the value be read-only by not providing a setter method.
Notifications. Other modules may wish to know when a value has changed in your class. For example, to implement a data model for a progress bar, the user interface code will want to know when the progress value has been updated so that it can update the GUI. To do this, issue a change notification as part of a setter method.
Caching. A classic optimization technique is to store the value of a frequently requested calculation and then directly return that value for future requests. For example, a machine's total memory size can be found on Linux by parsing the /proc/meminfo file. Instead of performing a file read for every request to find the total memory size, it would be better to cache the result after the first read and then simply return that cached value for future requests.
Synchronization. You may release the first version of your API and then later find that you need to make it thread safe. The standard way to do this is to add mutex locking whenever a value is accessed. This would only be possible if you have wrapped access to the data values in getter/setter methods.
Lazy evaluation. Calculating the value of a variable may incur a significant cost, which you would prefer to avoid until necessary. By using a getter method to access the underlying data value, you can defer the costly calculation until the value is actually requested.
Validation: Validations can be performed on the values to ensure that the internal state of the class is always valid and consistent.
Finer access control: By using getter/setter methods, a finer level of read/ write control can be provided. For example, you can make the value be read-only by not providing a setter method.
Notifications. Other modules may wish to know when a value has changed in your class. For example, to implement a data model for a progress bar, the user interface code will want to know when the progress value has been updated so that it can update the GUI. To do this, issue a change notification as part of a setter method.
Caching. A classic optimization technique is to store the value of a frequently requested calculation and then directly return that value for future requests. For example, a machine's total memory size can be found on Linux by parsing the /proc/meminfo file. Instead of performing a file read for every request to find the total memory size, it would be better to cache the result after the first read and then simply return that cached value for future requests.
Synchronization. You may release the first version of your API and then later find that you need to make it thread safe. The standard way to do this is to add mutex locking whenever a value is accessed. This would only be possible if you have wrapped access to the data values in getter/setter methods.
Lazy evaluation. Calculating the value of a variable may incur a significant cost, which you would prefer to avoid until necessary. By using a getter method to access the underlying data value, you can defer the costly calculation until the value is actually requested.
Example:
Identify the encapsulation issues in below class
Class IntegerStack
{
public:
static const int MAX_SIZE = 100;
voidPush(intval);
int Pop();
bool IsEmpty() const;
int mStack[MAX_SIZE];
int mCurSize;
};
Identify the encapsulation issues in below class
Class IntegerStack
{
public:
static const int MAX_SIZE = 100;
voidPush(intval);
int Pop();
bool IsEmpty() const;
int mStack[MAX_SIZE];
int mCurSize;
};
Answer:
- Reveals the implementation details of the class that the stack has been implemented as a fixed array of integers
- Exposes the internal state of the stack via the mCurSize variable.
- Clients has the ability to access the variables mStack and mCurSize directly,so any future change can break client’s code.
- Reveals the implementation details of the class that the stack has been implemented as a fixed array of integers
- Exposes the internal state of the stack via the mCurSize variable.
- Clients has the ability to access the variables mStack and mCurSize directly,so any future change can break client’s code.
Final version of the stack class is
Class IntegerStack
{
public:
voidPush(intval);
int Pop();
bool IsEmpty() const;
{
public:
voidPush(intval);
int Pop();
bool IsEmpty() const;
private:
static const int MAX_SIZE = 100;
int mStack[MAX_SIZE];
int mCurSize;
};
static const int MAX_SIZE = 100;
int mStack[MAX_SIZE];
int mCurSize;
};
Example2:
class URLDownloader
{
public:
URLDownloader();
bool DownloadToFile(const std::string &url,
const std::string &localFile);
bool SocketConnect(const std::string &host, int port);
void SocketDisconnect();
bool IsSocketConnected() const;
int GetSocket() const;
bool SocketWrite(const char *buffer, size_t bytes);
size_t SocketRead(char *buffer, size_t bytes);
bool WriteBufferToFile(char *buffer,
const std::string &filename);
private:
int mSocketID;
struct sockaddr_in mServAddr;
bool mIsConnected;
};
{
public:
URLDownloader();
bool DownloadToFile(const std::string &url,
const std::string &localFile);
bool SocketConnect(const std::string &host, int port);
void SocketDisconnect();
bool IsSocketConnected() const;
int GetSocket() const;
bool SocketWrite(const char *buffer, size_t bytes);
size_t SocketRead(char *buffer, size_t bytes);
bool WriteBufferToFile(char *buffer,
const std::string &filename);
private:
int mSocketID;
struct sockaddr_in mServAddr;
bool mIsConnected;
};
Answer:
Hide all methods that do not need to be public.
Routines to open and read data from a socket and a routine to write the resulting in-memory buffer to a file on disk. The client doesn't need to know any of this. All the client wants to do is specify a URL and then magically have a file created on disk with the contents of that remote location.
Routines to open and read data from a socket and a routine to write the resulting in-memory buffer to a file on disk. The client doesn't need to know any of this. All the client wants to do is specify a URL and then magically have a file created on disk with the contents of that remote location.
#include
class URLDownloader
{
public:
URLDownloader();
bool DownloadToFile(const std::string &url,
const std::string &localFile);
};
Hide Implementation Classes
In addition to hiding the internal methods and variables of the classes, also endeavor to hide any actual classes that are purely implementation detail. Classes which are only needed for the implementation should not be revealed as part of the public interface of your API.
{
public:
URLDownloader();
bool DownloadToFile(const std::string &url,
const std::string &localFile);
};
Hide Implementation Classes
In addition to hiding the internal methods and variables of the classes, also endeavor to hide any actual classes that are purely implementation detail. Classes which are only needed for the implementation should not be revealed as part of the public interface of your API.