Saturday, 24 October 2009

Object Orientation Design Principles

How to write a good code?

What is a good code and how can we write a more object oriented code?
Sometimes I heard my team members complaining about the quality of code which I wrote, so I started a search for some instructions about “What is a good code?”
I went through “Agile Priniciples, Patterns and Practices in C# by Robert C. Martin”, “Refactoring by Martin Fowler” and “Head first Design Patterns from O’Reilly press” and found some useful tips.

So before going through those tips, let’s start with what is bad code?
The most common criterion which is responsible for bad design(in real life) is “TNTWIWHDI” means “That’s not the way I would have done it” so this should not be a criteria for declaring a code piece be bad code. But the main criteria should be

a) Rigidity: - It is hard to change because every change affects too many other parts of the system.
b) Fragility: - When you make a change, unexpected parts of the system break.
c) Immobility: - It is hard to reuse in another application because it cannot be disentangled from the current application.
d) Viscosity: - It’s hard to do right things but easy to do wrong things.

For avoiding above causes and writing good code experts have accumulated 10 OOAD (Object orientation analysis and design) principles.

1) Open Closed Principle (OCP): - Code should be open for extension and close for modification.
2) Liskov Substitution Principle (LSP): - Every function which operates upon a reference or a pointer to a base class should be able to operate upon derivatives of that base class without knowing it.
3) Dependency Inversion Principle (DIP): - High level modules should not depend upon low level modules. Both should depend upon abstraction. Abstraction should not depend upon details. Details should depend upon abstraction.
4) Interface segregation Principle (ISP): - Client should not be forced to depend upon interfaces that they do not use. Many client specific interface are better then one general purpose interface.
5) Release Reuse Equivalency Principle (REP): - The granule of reuse is the granule of release. Users will be unwilling to use the element if they need or forced to upgrade every time author changes it.
6) Common Closure Principle (CCP): - Classes those changes together belong together. The more packages that change in any given release, the greater the work to rebuild, test and deploy the release. So they should be grouped together.
7) Common Reuse Principle (CRP): - Classes that are not used together should not be grouped together. A dependency upon a package is a dependency upon everything in the package. When a package changes, and its release number is bumped , all clients of that package must verify that they work with the new package even if nothing they used within package actually changed.
8) Acyclic Dependencies Principle (ADP): - The dependencies between packages must not form cycles.
9) Stable Dependencies Principle (SDP): - Depend in the direction of stability. A package with lots of incoming dependencies is very stable because it requires a great deal of work to reconcile any changes with all the dependent packages.
10) Stable Abstraction Principle (SAP): - Stable packages should be abstract packages. The SAP I sjust a restatement of the DIP. It states that the packages that are most dependent upon (i.e. stable) should also be most abstract.

So I think that if we all try to follow these principles then our code quality will be mush better and easy to maintain.
And one more thing most of these experts were convinced that a code following all the principles described above is not possible but we should try to follow most of them until there is a suitable reason for not following any.
Details of all of these design principles are availabe on www.objectmentor.com and book published by Robert C. Martin but i would still like to describe all of these on my blog and will try to relate the with different design patterns.

WCF Callback

Implementing callback in WCF services.

This is a way of writing asynchronous code using wcf services. But do we know why to do this explicitly when adding a service reference also gives you a facility of generating asynchronous operations? This is because the asynchronous operations which we generate while adding service reference are not fake asynchronous calls, in fact they are synchronous call only, just let code not to wait for the response/ notification and it has its timeout period and will throw a timeout exception if response/notification will not be received before timeout. So for implementing real asynchronous operations we like to implement callbacks using callback contract.

Not all bindings supports callback mechanism. We got 2 binding with the help of which we can implement callback

  1. netTcpBinding
  2. wsDualHttpBinding

There are 2 important things which make any binding to support callback

  1. binding must allow session
  2. binding protocol should allow call from server to client.

BasicHttpBinding does not support session so callbacks are not allowed and other http binding do not support callback because http protocol does not allow notification server to each on the on same port.

Then how wsDualHttpBinding supports callback?

While using wsDualHttpBinding client used to pass a callback address to server and server can use this address to notify back to client.

However netTcpBinding protocol allows notification from server on same port.

So we will see how we can implement callback using these 2 bindings. For wsDualHttpBinding we will host our application in IIS and for netTcpBinding we will host our application in a console, as in IIS 6.0 we can not host netTcpBinding applications.

For these implementations I have used one service contract and used with both kind of bindings and have used “Add Service Reference” mechanism to add service references in client. I have used Http Url to expose metadata of netTcpBinding service so use http Url to add service reference.

Using wsDualHttpBinding :

Service Contract:

// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.

[ServiceContract(CallbackContract = typeof(ICallback), SessionMode=SessionMode.Required)]

public interface ISampleService

{

[OperationContract(IsOneWay = true)]

void GetData(string value);

// TODO: Add your service operations here

}

public interface ICallback

{

[OperationContract(IsOneWay = true)]

void Notify(string value);

}

Server Implementation:

public class SampleService : ISampleService

{

private string result;

public void GetData(string value)

{

result = string.Format("You entered: {0}", value);

Thread.Sleep(5000);

OperationContext.Current.GetCallbackChannel<ICallback>().Notify(value);

}

}

Server Configuration:

<system.serviceModel>

<services>

<service name="wsDualHttpBinding.SampleService" behaviorConfiguration="wsDualHttpBinding.SampleServiceBehavior">

<endpoint address="" binding="wsDualHttpBinding" contract="wsDualHttpBinding.ISampleService">

<identity>

<dns value="localhost"/>

identity>

endpoint>

<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>

service>

services>

<behaviors>

<serviceBehaviors>

<behavior name="wsDualHttpBinding.SampleServiceBehavior">

<serviceMetadata httpGetEnabled="true"/>

<serviceDebug includeExceptionDetailInFaults="false"/>

behavior>

serviceBehaviors>

behaviors>

system.serviceModel>

Client Implementation:

public partial class TestForm : Form

{

public TestForm()

{

InitializeComponent();

}

private void button1_Click(object sender, EventArgs e)

{

InstanceContext instanceContext=new InstanceContext(new SampleServiceCallback());

SampleServiceProxy.SampleServiceClient sampleServiceClient = new SampleServiceClient(instanceContext);

sampleServiceClient.GetData("using wsDualHttpBinding,You have entered :"+textBox1.Text);

netTcpSampleServiceProxy.SampleServiceClient netTcpsampleServiceClient=new netTcpSampleServiceProxy.SampleServiceClient(instanceContext);

sampleServiceClient.GetData("using netTcpBinding, You have entered :"+textBox1.Text);

}

}

public class SampleServiceCallback : ISampleServiceCallback

{

#region ISampleServiceCallback Members

public void Notify(string value)

{

MessageBox.Show(value);

}

#endregion

}

Client Configuration:

<system.serviceModel>

<bindings>

<netTcpBinding>

<binding name="NetTcpBinding_ISampleService" closeTimeout="00:01:00"

openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"

hostNameComparisonMode="StrongWildcard" listenBacklog="10"

maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"

maxReceivedMessageSize="65536">

<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"

maxBytesPerRead="4096" maxNameTableCharCount="16384" />

<reliableSession ordered="true" inactivityTimeout="00:10:00"

enabled="false" />

<security mode="Transport">

<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />

<message clientCredentialType="Windows" />

security>

binding>

netTcpBinding>

<wsDualHttpBinding>

<binding name="WSDualHttpBinding_ISampleService" closeTimeout="00:01:00"

openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

bypassProxyOnLocal="false" clientBaseAddress="http://localhost:8881/wsDualHttpBinding"

transactionFlow="false" hostNameComparisonMode="StrongWildcard"

maxBufferPoolSize="524288" maxReceivedMessageSize="65536"

messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true">

<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"

maxBytesPerRead="4096" maxNameTableCharCount="16384" />

<reliableSession ordered="true" inactivityTimeout="00:10:00" />

<security mode="Message">

<message clientCredentialType="Windows" negotiateServiceCredential="true"

algorithmSuite="Default" />

security>

binding>

wsDualHttpBinding>

bindings>

<client>

<endpoint address="http://localhost/wsDualHttpBinding/SampleService.svc" binding="wsDualHttpBinding"

bindingConfiguration="WSDualHttpBinding_ISampleService" contract="SampleServiceProxy.ISampleService"

name="WSDualHttpBinding_ISampleService">

<identity>

<dns value="localhost" />

identity>

endpoint>

<endpoint address="net.tcp://localhost:9000/SampleService" binding="netTcpBinding"

bindingConfiguration="NetTcpBinding_ISampleService" contract="netTcpSampleServiceProxy.ISampleService"

name="NetTcpBinding_ISampleService">

<identity>

<dns value="localhost" />

identity>

endpoint>

client>

system.serviceModel>

Using netTcpBinding :

Service Contract:

// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.

[ServiceContract(CallbackContract = typeof(ICallback), SessionMode=SessionMode.Required)]

public interface ISampleService

{

[OperationContract(IsOneWay = true)]

void GetData(string value);

// TODO: Add your service operations here

}

public interface ICallback

{

[OperationContract(IsOneWay = true)]

void Notify(string value);

}

Server Implementation:

public class SampleService : ISampleService

{

private string result;

public void GetData(string value)

{

result = string.Format("You entered: {0}", value);

Thread.Sleep(5000);

OperationContext.Current.GetCallbackChannel<ICallback>().Notify(value);

}

}

Server Configuration:

<system.serviceModel>

<services>

<service name="wsDualHttpBinding.SampleService" behaviorConfiguration="wsDualHttpBinding.SampleServiceBehavior">

<endpoint address="net.tcp://localhost:9000/SampleService" binding="netTcpBinding" contract="wsDualHttpBinding.ISampleService">

<identity>

<dns value="localhost"/>

identity>

endpoint>

service>

services>

<behaviors>

<serviceBehaviors>

<behavior name="wsDualHttpBinding.SampleServiceBehavior">

<serviceMetadata httpGetEnabled="true"/>

<serviceDebug includeExceptionDetailInFaults="false"/>

behavior>

serviceBehaviors>

behaviors>

system.serviceModel>

Server Hosting:

ServiceHost host = new ServiceHost(typeof(SampleService), new Uri("http://localhost:8090/SampleService"));

host.Open();

ServiceEndpointCollection endpoints = host.Description.Endpoints;

foreach (ServiceEndpoint endpoint in endpoints)

{

Console.WriteLine("The 'SampleService' service host is listening at {0}", endpoint.Address);

}

Console.WriteLine("Press to terminate the service.");

Console.ReadLine();

Client Implementation:

Same as wsDualHttpBinding client implementation (As I have used same client for both kind of bindings.)

Client Configuration:

Same as wsDualHttpBinding client implementation (As I have used same client for both kind of bindings.)