Understanding object-oriented programming in C#
- select the contributor at the end of the page -
We all know how to create objects, get/set properties and call methods. But object-oriented programming (OOP) is much more than that. The more you know about these concepts, the more you can leverage the features of OOP languages, such as C#, to build well-crafted and testable applications.
This post covers four key techniques for building great object-oriented software, as identified in Figure 1. For more information on these and other pragmatic OOP techniques, check out my Pluralsight course, Object-Oriented Programming Fundamentals in C#. This course includes step-by-step demonstrations of building a class library component from specification through coding and testing, all following OOP principles.
Regardless of the formality of your development process, every project starts with some words:
- “Can you build a customer management system to help us track our customers and orders?”
- Or maybe, “Can you update our current accounting system to track our budget?”
- Or simply, “We need to change the system to track different types of products.”
Those words may come in the form of a specification, a change request or maybe just an email. The first step to developing great C# code is to turn these words into a set of well-defined classes.
Object-oriented programming involves logically separating the responsibilities of an application into classes. Each defined class should have one primary responsibility. By limiting each class to a singular purpose, it's easier to write, test and later find that class when you need to update or extend it. This makes the code easier to modify and adapt to new requirements and future demands. You may hear this referred to as “the principle of separation of concerns.”
No class is an island. Object-oriented programming involves understanding the relationships between the classes. The most common types of relationships are:
- Collaboration (“uses a”): An object uses the features of another object to accomplish a task. For example, a Customer Repository “uses a” Customer object to populate on a Retrieve and serialize on a save.
- Composition (“has a”): An object can be composed of other objects. For example, an Order “has a” customer, and an Order “has a” shipping address.
- Aggregation: A special type of composition whereby the component parts do not exist except as part of the composition. For example, the Order and Order Item relationship is an aggregate because the Order Item does not exist except when it is associated with a specific Order.
- Inheritance (“is a”): An object “is a” type of another object. For example, a Business Customer “is a” Customer, and a Residential Customer “is a” Customer.
These relationships define how the objects created from those classes can work together to perform the operations of the application.
One of the key goals of OOP is reuse. There are several ways to leverage reuse in an object-oriented application:
- Collaboration: Define a set of common classes and reuse those classes via collaboration. For example, an Address class could be reused anywhere an address is required.
- Inheritance: Extract common code into a base class and reuse that code in all derived classes. For example, an entity base class can contain basic code required for each business object entity in the application.
- Components: Build an encapsulated library of reusable classes as a separate component (Visual Studio project). For example, a library of String extension methods or a Logging component that can be reused across projects and across applications.
- Interfaces: Define an interface for each role that an entity needs to support. For example, a logging role or a serialization role. Reuse that interface in any entity that needs to support that role.
A side benefit of using inheritance or interfaces is polymorphism. Polymorphism is an impressive sounding word that basically means “many shapes.” It's the concept that a single method can have many shapes and can behave differently, depending on the type that called it.
In inheritance-based polymorphism, a method can behave differently depending on the derived class that calls it. For example, say that an entity base class has a Save method. If each entity overrides that Save method with its own implementation, calling Save on a Customer object will behave differently than calling Save on a Product object.
In interface-based polymorphism, a method can behave differently depending on the interface instance that calls it. For example, say that an ILoggable interface has a Log method. Assuming both the Customer and Product classes implement the ILoggable interface, calling Log on a Customer object can behave differently than calling Log on a Product object.
OOP is the foundation
Object-oriented programming is the foundation of many current application development approaches, as shown in Figure 2. If you want to succeed in math courses such as calculus and differential equations, you have to master algebra. Likewise, if you want to succeed with clean coding, agile and design patterns, you have to master OOP.
Understanding and using object-oriented coding techniques is the key to building well-crafted and testable C# applications.
For more information, check out my Pluralsight course Object-Oriented Programming Fundamentals in C#. This takes you step-by-step through the principles and practices of OOP. When you finish this course:
- You will have a firm foundation in object-oriented programming principles and practices.
- You will have the knowledge to build production-quality applications.
- And you will be ready to successfully advance to intermediate-level C# courses and techniques.
To learn more about what C# is used for check out this post on the matter.