Introduction
After decades of object-oriented programming (OOP), it is still a mainstream programming paradigm in software development. However, there are other programming paradigms, namely, functional programming (FP) and object decoration (OD). In this article, I discuss how OOP can be benefited from them.
OOP Challenges
Over the course of advancements of OOP, we have gotten to know it better for both its strength and weakness, and have more choices to address problems that OOP has been facing. There are a few challenges with OOP, i.e., dependency, extensibility and mutability.
Dependency
Dependency or coupling is the degree to which a software module relies on other modules. Whenever a class A uses another class or interface B, then A depends on B. A cannot carry out its work without B. Generally, interface dependency (loose coupling) is preferable over class dependency (tight coupling). Interface dependency is often desirable since it provides an abstract contract for client to follow without knowing the implementation details. On the other hand, class dependency is the root of all evil. It makes a system implementation-dependent and therefore difficult to change. Although it is possible to design a software system loosely-coupled initially, the challenge is how to keep it loosely-coupled while adding more functionality.
Extensibility
To add more functionality, you need to extend an existing software system. You can modify existing classes, or use inheritance, composition and design patterns to address changes. In general, you should choose composition over inheritance. Design patterns are often employed to solve common design problems for a complicate system while the use of them makes the system even more complicate. And they all need class design. The need of class design is a significant limit of OOP. It may break existing class dependencies or create new class dependencies, and needs significant compilation, testing and deployment efforts. The challenge for extensibility is how to avoid class design while adding more functionality.
Mutability
The result of an object operation depends on not only the arguments that are input to the operation but also the state of the executing program. It makes difficult to predict the behaviors of a software system. Although a certain degree of mutability may be inevitable, it is desired to avoid object mutability in object operations. The challenge is how to extend a software system without altering object state in object operations.
Solutions
The above challenges cannot be resolved effectively by OOP itself. In the following sections, I discuss how to address these challenges by combining technologies in OOP with other paradigms like functional programming and object decoration.
Programming to interface
By following Dependency Inversion Principle and using an IoC container, you can start programming to interfaces exposed by objects instantiated from the IoC container. Assuming that class Employee
implements interface IEmployee
, the following code registers the Employee
class to UnityContainer
with the IEmployee
.
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IEmployee, Employee>(new InjectionConstructor());
Now, you can ask the unityContainer
for an Employee
object by specifying the interface IEmployee
as follows.
IEmployee e = unityContainer.Resolve<IEmployee>();
The rest of your program can start using the interface e
without knowing it is an object of Employee
class. Later on, let’s say you have a new class Employee2
, which also implements the interface IEmployee
. To replace Employee
with Employee2
, just modify the line of registering code as follows.
unityContainer.RegisterType<IEmployee, Employee2>(new InjectionConstructor());
Now, your program starts to use the new class Employee2
. It makes component replacement extremely easy.
impromptu-interface: IoC containers are convenient if you know a class has implemented an interface. What about a class that doesn’t implement an interface? Of course, you can not program to interface since it simply doesn’t have interface to program. But wait! You can use impromptu-interface. With the impromptu-interface, any object can be wrapped with an interface. Say, you have a class Order
defined as follows.
public class Order
{
public int OrderID { get; set; }
public int CustomerID { get; set; }
public DateTime DueDate { get; set; }
public string AccountNumber { get; set; }
public int ContactID { get; set; }
public int BillToAddressID { get; set; }
public int ShipToAddressID { get; set; }
public int ShipMethodID { get; set; }
public double SubTotal { get; set; }
public double TaxAmt { get; set; }
public SqlCommand Command { get; set; }
public int InsertOrder()
{
return 0;
}
}
To use impromptu-interface, you need to define an interface first. Let’s say you want all methods of the class be interface methods. You define the interface as follows.
public interface IOrder
{
int OrderID { get; set; }
int CustomerID { get; set; }
DateTime DueDate { get; set; }
string AccountNumber { get; set; }
int ContactID { get; set; }
int BillToAddressID { get; set; }
int ShipToAddressID { get; set; }
int ShipMethodID { get; set; }
double SubTotal { get; set; }
double TaxAmt { get; set; }
SqlCommand Command { get; set; }
int InsertOrder();
}
Then, you use the following code to instantiate an Order
object and wrap it in the interface IOrder
.
IOrder iOrder = new Order().ActLike<IOrder>();
Now, you can use the interface iOrder
in your program.
With impromptu-interface, interface can be designed after class design. You design your classes with or without interfaces. Remember an interface is a service contract to clients of a component. Sometimes, it is not clear what service contract a component should provide during design of business classes. At some other times, it seems making perfect sense to design a class without interface. Impromptu-interface gives clients of business classes the flexibility to wrap an object in an interface. After all, it is the client that knows what services it needs, not the other way around. Impromptu-interface makes possible program to interface as needed.
Object Decoration
Programming to interface is great for creation of a loosely-coupled software system in terms of use of existing functionality of business classes. Now, what about adding extra functionality? Of course, you can modify the existing classes or create new classes. But wait! You are modifying the existing class dependencies or creating new class dependencies. You should avoid doing this whenever possible.
So, what is the other option then? The answer is object decoration (OD). OD attaches extra behaviors to an existing object. The following code adds entering log and exiting log to the method InsertOrder
of the object iOrder
.
iOrder = ObjectProxyFactory.CreateProxy2<IOrder>(
iOrder,
new string[] { "InsertOrder" },
new Decoration2((x, y) => { System.Console.WriteLine("Enter"); }, null),
new Decoration2((x, y) => { System.Console.WriteLine("Exit"); }, null)
);
Now, the following code writes entering log, then, inserts an order, and last, writes exiting log.
iOrder.InsertOrder();
Here are a few interesting points regarding OD:
- It is client-side programming (no class modifications or creations).
- It works with object and object’s interface methods (programming to interface).
- It is functional programming (behaviors are functions).
OD extends software by avoiding class design and therefore not changing/adding dependencies of a software system. Functions as behaviors means not increasing mutability of a software system. It is a simple yet very powerful approach for software extensibility. To see how logging, security checking and sorting are added to objects using OD, click here. To see how transaction is added to objects using OD, click here.
Conclusions
Both programming to interface and object decoration emphasize on client-side programming and are independent of server-side implementation.
Impromptu-interface makes possible programming to interface as needed.
Object decoration makes possible adding behaviors as needed.
Programming to interface with object decoration solves dependency, extensibility and mutability of OOP effectively. It makes possible a loosely-coupled system all the way through its life cycle.
Programming to interface and object decoration reduce class design but do not get rid of class design. You still need to design a business class if existing ones do not meet business requirements. The key point here is that once a business class is designed appropriately, you should keep it from changes. The programming to interface and object decoration help to achieve this.