Click here to Skip to main content
15,885,309 members
Articles / Programming Languages / C#

S.O.L.I.D, GRASP and Other Basic Principles of Object Oriented Design

Rate me:
Please Sign up or sign in to vote.
4.42/5 (29 votes)
12 Dec 2018CPOL21 min read 73.3K   304   80   13
Learn SOLID, GRASP and other hard-core object oriented design OOD principles using a language-independent and in a simple manner to impress other developers

Introduction

I will start with a cliché.

A software code should depict the following qualities:

  1. Maintainability
  2. Extensibility
  3. Modularity
  4. etc.

You may find yourself in a difficult situation when you ask a question about whether any particular code depicts the above quality features or not.

A technique that helps is by looking at the development timeline of any software. If the software code remains easier to maintain, extend and modular over its lifetime, then it means that the code has the above quality features.

I have written difficult to read, hard to extend and rotten software code. I only knew this after six months into the development when a change happens. Hence, development timeline is important in understanding quality factors.

But this technique (looking at the timeline of development) can only be applied by looking into the past but we want a quality software somewhere in future.

Senior developers, who know about quality, don't have this problem. They feel proud when they see their code has the quality factors which junior developers only dream about.

Hence, senior developers and experts have distilled down a set of principles which junior developers can apply to write a good quality code and to show off in front of other developers :). If you want to write your principles, then here is a guide to extract principles from your own experience.

In this post, I will cover SOLID principles. These principles are given by Uncle Bob (Robert C. Martin). I will also cover GRASP (General Responsibility Assignment software Principles) published by Craig Larman and other basic object oriented design principles. I have included examples from my personal experience, therefore, you will not find any 'Animal' or 'Duck' examples.

The code examples shown are closer to Java and C# but they are helpful to any developer who knows the basics of object oriented programming.

Here is the complete list of principles covered in this post:

  1. Single Responsibility Principle (SOLID)
  2. High Cohesion (GRASP)
  3. Low Coupling (GRASP)
  4. Open Closed Principle (SOLID)
  5. Liskov Substitution principle (SOLID)
  6. Interface Segregation Principle (SOLID)
  7. Dependency Inversion Principle (SOLID)
  8. Program to an Interface, Not to an Implementation
  9. Hollywood Principle
  10. Polymorphism (GRASP)
  11. Information Expert (GRASP)
  12. Creator (GRASP)
  13. Pure Fabrication (GRASP)
  14. Controller (GRASP)
  15. Favor Composition Over Inheritance
  16. Indirection (GRASP)
  17. Don't Repeat Yourself

This article is huge and I want you to read the complete article. One easy way is that you can bookmark the article (requires login). In this way, you can continue reading afterwards. Also, I have make a PDF of this article which you can download that from the top of this article.

Single Responsibility Principle

SRP says:

Quote:

A class should have only one responsibility.

A class fulfills its responsibilities using its functions or contracts (and data members help functions).

Take the following example class:

C#
Class Simulation{
Public LoadSimulationFile()
Public Simulate()
Public ConvertParams()
}

This class handles two responsibilities. First, this class loads simulation data and second, it performs the simulation algorithm (using Simulate and ConvertParams functions).

A class fulfills a responsibility using one or more than one function. In the example above, loading simulation data is one responsibility and performing simulation is another responsibility. One function is needed to load simulation data (i.e., LoadSimulationFile). Remaining two functions are needed to perform the simulation.

How do we know how many responsibilities my class has? Consider the phrase "reason to change" analogous to responsibility. Hence, look for all the reasons a class has to change. If there is more than one reason to change a class, then it means this class does not follow the single responsibility principle.

In our example class above, this class should not contain LoadSimulationFile function (or loading simulation data responsibility). If we create a separate class for loading simulation data, then this class will not violate SRP.

A class can only have a single responsibility. How would you design a software with such a hard rule?

Let’s consider another principle which is closely related to SRP and it is called High Cohesion. High cohesion gives you a subjective scale and not an objective one as in the case of SRP.

Very low cohesion means a class is fulfilling many responsibilities. For example, there are more than 10 responsibilities for which a class is responsible.

Low Cohesion means a class fulfills around 5 responsibilities and moderate cohesion means a class fulfills 3 responsibilities. High Cohesion means a class fulfills a single responsibility.

Hence, the rule of thumb is while designing, strive for high cohesion.

Another principle which should be discussed here is Low Coupling. This principle states that one should assign a responsibility so that the dependency between the classes remains low.

Again, consider the above example class. After applying SRP and high cohesion principle, we decided to make a separate class which will handle simulation files. In this way, we created two classes which are dependent on each other.

It looks like that applying high cohesion causes us to violate another principle which is low coupling. This level of coupling is allowed as the goal is to minimize the coupling not zeroing the coupling. Some degree of coupling is normal to create an object-oriented design in which tasks are completed by a collaboration of objects.

On the other hand, consider a GUI class which connects to a database, handles remote clients over HTTP and handles screen layout. This GUI class is dependent upon too many classes. This GUI class clearly violates the low-coupling principle. This class cannot be re-used without involving all the related classes. Any change to database component cause changes to GUI class.

Open-Closed Principle

Open-Closed principles says:

Quote:

A software module (it can be a class or method) should be open for extension but closed for modification.

In simple words, you cannot update the code that you have already written for a project, but you can add new code to the project.

There are two methods through which you can apply the open-closed principle. You can apply this principle either through inheritance or through composition.

Here is the example of applying open-close principle using inheritance:

C#
Class DataStream{
	Public byte[] Read()
}	

Class NetworkDataStream:DataStream{
	Public byte[] Read(){
	//Read from the network
 }
}

Class Client {
	Public void ReadData(DataStream ds){
		ds.Read();
 }
}

In this example, the client read data (ds.Read()) from the network stream. If I want to extend the functionality of client class to read data from another stream, e.g., PCI data stream, then I will add another subclass of DataStream class as shown in the listing below:

C#
Class PCIDataStream:DataStream{

	Public byte[] Read(){
	//Read data from PCI
 }
}

In this scenario, the client code will function without any error. Client class knows about the base class and I can pass an object of any of the two subclasses of DataStream. In this way, the client can read data without knowing the underlying subclass. This is achieved without modifying any existing code.

We can apply this principle using composition and there are other methods and design patterns to apply this principle. Some of these methods will be discussed in this post.

Do we have to apply this principle to every piece of code that we write? The answer is no. This is because most of that code will not change. You will have to apply this principle strategically in those conditions where you suspect a piece of code will change in the future.

In the previous example, I know from experience of my domain that there will be more than one stream. Therefore, I apply open-closed principle so that it can handle future changes without modification.

Liskov Substitution Principle

LSP says:

Quote:

Derived classes must be substitutable for their base classes.

Another way to look at this definition is that abstraction (interface or abstract class) should be enough for a client.

To elaborate, let’s consider an example, here is an interface whose listing is given below:

C#
Public Interface IDevice{
	Void Open();
	Void Read();
	Void Close();
}

This code represents data acquisition device abstraction. A data acquisition device differentiates based upon its interface type. A data acquisition device can use USB interface, Network Interface(TCP or UDP), PCI express interface or any other computer interface.

Clients of IDevice do not need to know what kind of device they are working with. This gives programmers enormous flexibility to adapt to new devices without changing the code which depends upon the IDevice interface.

Let’s go into a little history when there were only two concrete classes that implemented IDevice interface shown below:

C#
public class PCIDevice:IDevice {
   public void Open(){
   // Device specific opening logic
   }
   public void Read(){
   // Reading logic specific to this device
   }
   public void Close(){
   // Device specific closing logic.
   }
}

public class NetWorkDevice:IDevice{

 public void Open(){
 // Device specific opening logic
 }
 public void Read(){
 // Reading logic specific to this device
 }
 public void Close(){
 // Device specific closing logic.
 }
}

These three methods (open, read and close) are sufficient to handle data from these devices. Later, there was a requirement to add another data acquisition device which was based upon USB Interface.

The problem with USB Device was that when you open the connection, data from the previous connection remains in the buffer. Therefore, upon the first read call to the USB device, data from the previous session returned. That behavior corrupted data for that particular acquisition session.

Fortunately, USB based device driver provides a refresh function which clears the buffers in the USB based acquisition device. How can I implement this feature into my code so that code change remains minimal?

One naive solution is to update the code by identifying if you are calling the USB object:

C#
public class USBDevice:IDevice{
 public void Open(){
    // Device specific opening logic
 }
 public void Read(){
    // Reading logic specific to this device<br> 
 }
public void Close(){
   // Device specific closing logic.
 }
 public void Refresh(){
   // specific only to USB interface Device
 }
}

//Client code...

Public void Acquire(IDevice aDevice){
        aDevice.Open();
        // Identify if the object passed here is USBDevice class Object.    
	if(aDevice.GetType() == typeof(USBDevice)){
	USBDevice aUsbDevice = (USBDevice) aDevice;
	aUsbDevice.Refresh();
	}

        // remaining code….
}

In this solution, client code is directly using the concrete class as well as the interface (or abstraction). It means abstraction is not enough for the client to fulfill its responsibilities.

Another way to state the same thing, the base class cannot fulfill the required behavior (refresh behavior) but the derived class has this behavior. Hence derived class is not compatible with the base class and therefore derived class cannot be substituted. Hence this solution violates Liskov Substitution Principle.

In the example above, the client is dependent upon more entities (IDevice and USBDevice) and any change in one entity will cause a change in other entities. Therefore, violation of LSP causes dependency between the classes.

A solution to this problem that follows LSP? I updated the Interface in this way:

C#
Public Interface IDevice{
	Void Open();
	Void Refresh();
	Void Read();
	Void Close();
}

Now the client of IDevice is:

C#
Public void Acquire(IDevice aDevice)
	{
	aDevice.open();
	aDevice.refresh();
	aDevice.acquire()
	//Remaining code...
}

Now client does not depend upon the concrete implementation of IDevice. Hence in this solution, our interface (IDevice) is enough for the client.

There is another angle to look at the LSP principle within the context of object oriented analysis. In summary, during OOA, we think about the classes and their hierarchies that could be part of our software.

When we are thinking about the classes and hierarchies, we can come up with classes which violate LSP.

Let’s consider the classic example of rectangle and square which is misquoted many times. When looking from the outset, it looks like that square is a specialized version of the rectangle and a happy designer would draw the following inheritance hierarchy.

C#
Public class Rectangle{
	Public void SetWidth(int width){}
	Public void SetHeight(int height){}
}

Public Class Square:Rectangle{
//
	
}

What happens next is you cannot substitute square object in place of rectangle object. Since Square inherits from Rectangle, therefore, it inherits its method setWidth() and setHeight(). A client of Square object can change its width and height to different dimensions. But width and height of a square are always identical, hence failing the normal behavior of the software.

This can only be avoided by looking at classes according to different usage scenarios and conditions. Therefore, when you are designing classes in isolation, there is a probability that your assumptions may fail. As in the case of Square and Rectangle, is-a relationship looks good enough during initial analysis but as we look at different conditions, this is-a relationship failed the correct behavior of the software.

Interface Segregation Principle

Interface Segregation Principle (ISP) says:

Quote:

Clients should not be forced to depend upon the interfaces that they do not use.

Again, consider the previous example:

C#
Public Interface IDevice{
	Void Open();
	Void Read();
	Void Close();
}

There are three classes that implement this interface. USBDevice, NetworkDevice, and PCIDevice. This interface is good enough to work with network and PCI devices. But USB device needs another function (Refresh()) to work properly.

Similar to USB device, in future, there can another device which may require refresh function to work properly. Due to this, IDevice is updated as shown below:

C#
 Public Interface IDevice{
	Void Open();
        Void Refresh();
	Void Read();
	Void Close();
}

The problem is now each class that implements IDevice has to provide the definition for the refresh function.

For example, I have to add the following lines of code to NetworkDevice class and PCIDevice class to work with this design:

C#
public  void Refresh()
{
// Yes nothing here… just a useless blank function
}

Hence, IDevice represents a fat interface (too many functions). This design violates the Interface Segregation Principle because the fat interface is causing unnecessary clients to depend upon it.

There are numerous ways to solve this problem, but I will tackle this problem using my domain specific knowledge.

I know that refresh is directly called after the open function. Hence, I moved the logic of refresh from the client of IDevice to the specific concrete class. In our case, I moved the call to refresh logic to USBDevice class as shown here:

C#
Public Interface IDevice{
		Void Open();
		Void Read();
		Void Close();
}	

Public class USBDevice:IDevice{
	Public void Open{
	// open the device here…

	// refresh the device
	this.Refresh();
	
 }
Private void Refresh(){
	// make the USb Device Refresh
 }
}

In this way, I have reduced the number of functions in the IDevice class and make it less fat.

Dependency Inversion Principle

This principle is a generalization of other principles. Principles discussed above, LSP and OCP, substitutes dependency inversion principle.

Before jumping to the textbook definition of DIP, let me introduce a closely related principle which will help understand DIP.

The principle is:

Quote:

“Program to an interface, not to an implementation”

This is quite a simple one. Consider the following example:

C#
Class PCIDevice{
	Void open(){}
	Void close(){}
}
Static void Main(){
	PCIDevice aDevice = new PCIDevice();
	aDevice.open();
	//do some work
	aDevice.close();
	
}

The above example violates the “Program to an interface principle” because we are working with the reference of concrete class PCIDevice. The following listing follows this principle:

C#
Interface IDevice{
	Void open();
	Void close();	
}
Class PCIDevice implements IDevice{
	Void open(){ // PCI device opening code }
	Void close(){ // PCI Device closing code }
}
Static void Main(){
	IDevice aDevice = new PCIDevice();
	aDevice.open();
	//do some work
	aDevice.close();	
}

Hence, it’s very easy to follow this principle. Dependency inversion principle is similar to this principle but DIP asks us to do one more step.

DIP says:

Quote:

High-level modules should not depend upon low-level modules. Both should depend upon abstractions.

You can easily understand the line “Both should depend upon abstractions” as it is saying each module should program to an interface. But what are high-level modules and low-level modules?

To understand the first part, we have to learn what are high-level modules and low-level modules?

See the following code:

C#
Class TransferManager{
	public void TransferData(USBExternalDevice usbExternalDeviceObj,SSDDrive  ssdDriveObj){
            Byte[] dataBytes = usbExternalDeviceObj.readData();

           // work on dataBytes e.g compress, encrypt etc..

            ssdDriveObj.WrtieData(dataBytes);		
        }
}

Class USBExternalDevice{
	Public byte[] readData(){
        }
}

Class SSDDrive{
	Public void WriteData(byte[] data){
	}
}

In this code, there are three classes. TransferManager class represent a high-level module. This is because it is using two classes in its one function. Hence, other two classes are low-level modules.

High-level module function (TransferData) defines logic how data transferred from one device to another device. Any module which is controlling the logic, and uses the low-level modules in doing so, is called the high-level module.

In the code above, high-level module is directly (without any abstraction) using the lower level modules, hence violating dependency inversion principle.

Violation of this principle causes the software difficult to change. For example, if you want to add other external devices, you will have to change the higher level module. Hence, your higher level module will be dependent upon the lower level module and that dependency will make the code difficult to change.

The solution is easy if you understand the principle above "program to an interface". Here is the listing:

C#
Class USBExternalDevice implements IExternalDevice{
	Public byte[] readData(){
	}
}

Class SSDDrive implements IInternalDevice{
	Public void WriteData(byte[] data){
	}
}

Class TransferManager implements ITransferManager{
	public void Transfer(IExternalDevice externalDeviceObj, IInternalDevice internalDeviceObj){
           Byte[] dataBytes = externalDeviceObj.readData();

           // work on dataBytes e.g compress, encrypt etc..

           internalDeviceObj.WrtieData(dataBytes);		
        }
}

Interface IExternalDevice{
        Public byte[] readData();
}

Interface IInternalDevice{
	Public void WriteData(byte[] data);
}

Interface ITransferManager {
	public void Transfer(IExternalDevice usbExternalDeviceObj,SSDDrive  IInternalDevice);
}

In the code above, high-level module and low-level modules, both depend upon abstractions. This code follows Dependency Inversion Principle.

Hollywood Principle

This principle is similar to the dependency inversion principle. This principle says:

Quote:

Don’t call us, we will call you

This means a high-level component can dictate low-level components (or call them) in a manner that they are not dependent on each other.

This principle defends against dependency rot. Dependency rot happens when each component depends upon every other component. In other words, dependency rot is when dependency happens in each direction (Up, sideways, downward). Hollywood Principle restricts us to make dependency in only one direction.

The difference with dependency inversion principle is that DIP gives us a general guideline “Both higher level and lower component should depend upon abstractions and not on concrete classes”. On the other hand, Hollywood Principle specifies how higher level component and lower level component interact without creating dependencies.

Polymorphism

What--Polymorphism is a design principle? But we have learned that polymorphism is a basic feature of an object oriented programming.

Yes, it's the basic requirement for any OOP language to provide polymorphism feature where derived classes can be referenced through parent classes.

It’s also a design principle in GRASP. This principle provides guidelines about how to use this OOP language feature in your object oriented design.

This principle restricts the use of run-time type identification(RTTI). We implement RTTI in C# in the following manner:

C#
if(aDevice.GetType() == typeof(USBDevice)){

	//This type is of USBDEvice
}

In Java, RTTI is accomplished using the function getClass() or instanceOf().

C#
if(aDevice.getClass() == USBDevice.class){
    // Implement USBDevice
     Byte[] data = USBDeviceObj.ReadUART32();
}

If you have written this type code in your project, then now is the time to refactor that code and improve it using the polymorphism principle.

Look at the following diagram:

polymorphism diagram

Here, I have generalized the read method in the interface and delegate the device specific implementation to their classes (e.g., ReadUART32() in USBDevice).

Now I just use the method read.

C#
//RefactoreCode
IDevice aDevice = dm.getDeviceObject();
aDevice.Read();

Where will the implementation of getDeviceObject() come from? This we will discuss in the Creator principle and Information Expert principle where you will learn about assigning the responsibilities to classes.

Information Expert

This is a simple GRASP principle and gives guidelines about giving responsibilities to classes.

It says assign a responsibility to the class which has the information necessary to fulfill that responsibility.

Consider the following classes:

Information Expert

In our scenario, simulation is performed at full speed (600 loops per second) whereas user display is updated at reduced speed. Here, I have to assign a responsibility whether to display the next frame or not.

Which class should handle this responsibility? I have two options, either the simulation class or SpeedControl class.

Now SpeedControl class has the information about which frames has been displayed in the current sequence therefore according to the information expert SpeedControl should have this responsibility.

Creator

Creator is a GRASP principle and helps in deciding which class should be responsible for creating a new instance of a class.

Object creation is an important process and it is useful to have a principle in deciding who should create an instance of a class.

According to Larman, a class “B” should be given the responsibility to create another class “A” if any of the following conditions are true.

  1. B contains A
  2. B aggregates A
  3. B has the initializing data for A
  4. B records A
  5. B closely uses A

In our example of polymorphism, I have used Information Expert and Creator principles to give DeviceManager class the responsibility to create a Device object (dm.getDeviceObject()). This is because DeviceManger has the information to create a Device object.

Pure Fabrication

In order to understand Pure Fabrication, a pre-requisite is that you understand object oriented analysis (OOA).

In summary, object oriented analysis is a process through which you can identify the classes in your problem domain. For example, domain model for a banking system contains classes like Account, Branch, Cash, Check, Transaction, etc.

In the banking example, domain classes need to store information about the customers. In order to do that, one option is to delegate data storage responsibility to domain classes. This option will reduce the cohesiveness of the domain classes (more than one responsibility). Ultimately, this option violates the SRP principle.

Another option is to introduce another class which does not represent any domain concept. In the banking example, we can introduce a class “PersistenceProvider”. This class does not represent any domain entity. The purpose of this class is to handle data storage functions. Therefore “PersistenceProvider” is a pure fabrication.

Controller

When I started development, I wrote most of the program using Java's swing components and I wrote most of my logic behind listeners.

Then I learned about domain model. Hence, I moved my logic from listeners to Domain model. But I directly called the domain objects from listeners. This creates a dependency between the GUI components (listeners) domain model. Controller design principle helped in minimizing the dependency between GUI components and Domain Model classes.

There are two purposes of Controller. The first purpose of Controller is to encapsulate a system operation. A system operation is something that your user wants to achieve, e.g., buying a product or entering an item into the cart. This system operation is then accomplished by calling one or more method calls between the software objects. The second purpose of Controller is to provide a layer between UI and Domain Model.

A UI enables users to perform system operations. A controller is the first object after the UI layer that handles the system operations request and then delegates the responsibility to the underlying domain objects.

For example, here is MAP Class which represent a Controller in one of our software code.

controller

From the UI, we delegate the responsibility of "moving cursor" to this controller and then this calls the underlying domain objects to move the cursor.

By using the Controller principle, you will have the flexibility to plug in another user interface like command line interface or web interface.

Favor Composition Over Inheritance

Primarily, there are two tools in object oriented programming to extend the functionality of existing code. The first one is inheritance.

The second method is composition. In programming words, by having a reference to another object, you can extend that object’s functionality. If using composition, add a new class, create its object and then use that object using its reference where you want to extend the code.

A very helpful feature of the composition is that behavior can be set at run-time. On the other hand, using inheritance, you can only set the behavior at compile time. This will be shown in the example below.

When I was a newbie and used inheritance to extend the behavior, these are the classes that I designed:

composition vs inheritance

Initially, I only know about processing an incoming stream of data and there were two kinds (Stream A and Stream B) of data. After a few weeks, I came to know that endianness of the data should be handled. Hence, I come up with a class design shown below:

composition vs inheritance

Later on, another variable was added to the requirements. This time, I have to handle the polarity of the data. Imagine how many classes I have to add? Two types of polarity for streamA, streamB, Stream with endianness and so on. A class explosion! Now I will have to maintain a large number of classes.

Now, if I handle the same problem using composition, following will be the class design:

composition vs inheritance

I add new classes and then use them in my code using their references, see the listing below:

C#
clientData.setPolarity(new PolarityOfTypeA);    // or clientData.setPolarity(new PolarityOfTypeB)
clientData.FormatPolarity;

clientData.setEndianness(new LittleEndiannes());// setting the behavior at run-time
clientData.FormatStream();

Hence, I can provide the instances of the classes according to the behavior that I want. This feature reduced the total number of classes and ultimately maintainability issues. Hence, favoring composition over inheritance will reduce maintainability problems and flexibility to set behavior at run-time.

Indirection

This principle answers one question:

How to let objects interact in a manner that the bond among them remains weak?

The solution is:

Give the responsibility of interaction to an intermediate object so that the coupling among different components remains low.

For example:

A software application works with different configuration and options. To decouple the domain code from the configuration, a specific class is added shown in the following listing:

C#
Public Configuration{
  public int GetFrameLength(){
    // implementation
  }
  public string GetNextFileName(){

  }
 // Remaining configuration methods
}

In this way, if any domain object wants to read a certain configuration setting, it will ask the Configuration class object. Therefore, main code is decoupled from the configuration code.

If you have read the Pure Fabrication Principle, this Configuration class is an example of pure fabrication. But the purpose of indirection is to create de-coupling. On the other hand, the purpose of pure fabrication is to keep the domain model clean and represent only domain concept and responsibilities.

Many software design patterns like Adapter, Facade and observer are specializations of Indirection principle.

Don't Repeat Yourself (DRY)

Don't repeat yourself means don't try to write the same code again and again. The idea is if you are writing a few lines of code again and again, then you should combine them in one function and then call that function.

The biggest benefit is that now if you want to update those particular lines of code, you can update that in just one place. Otherwise, you will have to search all the places where code is written.

I have been hesitant to apply this principle. This is because in an old programming book, I have read that writing a separate function will cause your processor to work extra. For example, when you call a function, there is always an additional call in the assembly language and that is called 'JUMP' call.

This jump call incurs additional cost of execution. Now if the function is in a loop that is executing a 1 million times, that means that you have 1 million extra instructions to be executed by the processor.

Hmmm. Costly!!

This hindered me for a long period of time. There is a solution to that too. Now the compilers are so much optimized that they don't jump to the function. Instead, when you call a function, these compilers just replace the function calls with the actual lines of code. Hence, when the processor is running, there is no additional cost of 'JUMP'.

Everything else is also taken care by the compiler. So use the DRY principles as much as you like but make sure that your compiler is smart enough :).

Over to You

Tell me in the comments, what you liked in this post and what are your biggest challenges while learning and applying object oriented design principles.

I have another related article on CodeProject:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Pakistan Pakistan
If you want to learn more about object-oriented design, programming using real-life and real-world examples then you should visit:

www.linesperday.com

You will know what it takes to be great at programming and what lies ahead in your career: Is that money, authority or fame?

My name is Muhammad Umair and I write about programming, programmers, and object-oriented design and how you can optimize your programming skills to advance your programming career.

Comments and Discussions

 
GeneralMy vote of 5 Pin
MRezaK31-Mar-18 1:45
MRezaK31-Mar-18 1:45 
Questionpictures Pin
Member 1244887312-Jan-18 23:16
Member 1244887312-Jan-18 23:16 
QuestionPolymorphism - a query Pin
Tony Jacob Valavi19-Jan-17 20:00
Tony Jacob Valavi19-Jan-17 20:00 
QuestionSOLID is severely misinterpreted... PinPopular
PureNsanity18-Jan-17 19:21
professionalPureNsanity18-Jan-17 19:21 
AnswerRe: SOLID is severely misinterpreted... Pin
omeecode19-Jan-17 3:29
omeecode19-Jan-17 3:29 
GeneralRe: SOLID is severely misinterpreted... Pin
PureNsanity19-Jan-17 4:16
professionalPureNsanity19-Jan-17 4:16 
GeneralRe: SOLID is severely misinterpreted... Pin
omeecode19-Jan-17 7:39
omeecode19-Jan-17 7:39 
GeneralRe: SOLID is severely misinterpreted... Pin
PureNsanity19-Jan-17 10:10
professionalPureNsanity19-Jan-17 10:10 
You do not understand LSP at all.

I'm a 15 year, architect level developer with a 140 IQ. Sure I don't know everything, but I've been only presenting definitions. Nothing you discuss in the article, or i have brought up, is a heuristic argument...

Your loss if you don't see that... There are literal definitions to the terms you're presenting, it's the interpretation of them that can be faulty

P.S. The Rectangle vs Square is highlighting poor coding choices based on logical assumptions, not that it can't be done or shouldn't hold true.
GeneralRe: SOLID is severely misinterpreted... Pin
DavidSherwood26-Jan-17 13:58
DavidSherwood26-Jan-17 13:58 
GeneralRe: SOLID is severely misinterpreted... Pin
PureNsanity30-Jan-17 7:39
professionalPureNsanity30-Jan-17 7:39 
GeneralRe: SOLID is severely misinterpreted... Pin
Member 1352280531-Dec-18 5:15
Member 1352280531-Dec-18 5:15 
QuestionIn your code examples... Pin
Marc Clifton18-Jan-17 4:41
mvaMarc Clifton18-Jan-17 4:41 
AnswerRe: In your code examples... Pin
omeecode18-Jan-17 7:51
omeecode18-Jan-17 7:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.