Create a robust OO model in .NET with Rider

Posted on by Rachel Appel

Creating object-oriented models and systems is at the heart of .NET development. An object model is a language neutral logical mapping between digital objects and real life objects. They often represent objects that are required for a business, such as customers, accounts, or employees. Models enable the creation of an architecture prior to development, as well as concrete programming constructs during the development phase.

In this post, we’ll create an object model for a fictitious company’s HR system.

Objects and classes

Objects are representations of entities that exist outside of the application. At design time, object models are expressed as classes. Each class serves as a blueprint or template for one or more individual entities.

At runtime, objects are individual instances of the classes. Classes and objects contain data (properties) and behaviour (methods), just like entities in the physical world. Once you have designed a model, Rider makes creating the classes and their properties and methods easy.

Let’s consider a common scenario. The Human Resources Director asks us to create software to work with their HR system. The software allows HR people and managers to read and edit information about full time employees such as their vacation days, salary, awards, bonuses, and other information.

We start by creating a class called FullTimeEmployee with several fields, Perk and Role classes, and a Level enum to designate their employment level. As requirements change, we’ll change this code using Rider’s refactoring and intention actions.

public class FullTimeEmployee
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public DateTime HireDate { get; set; }
    public DateTime EndDate { get; set; }
    public Level Level { get; set; }
    public double Salary { get; set; }
    public double Bonus { get; set; }
    public Role Role { get; set; }
    public FullTimeEmployee ReportsTo { get; set; }
    public double TotalVacation { get; set; }
    public double VacationUsed { get; set; }
    public double VacationRemaining { get; set; }
    public Liste Benefits { get; set; }

public enum Level
    Junior = 0,
    Middle = 1,
    Senior = 2,
    Principal = 3,
    Partner = 4,
    Executive = 5

public class Benefit
    public int Id { get; set; }
    public string Title { get; set; }
    public double Value { get; set; }
    public double Amount { get; set; }

public class Role
    public int Id { get; set; }
    public string Title { get; set; }
    public string Duties { get; set; }
    public double SalaryRange { get; set; }

Properties and methods

In our scenario, HR tells us that employees get 20 days of vacation time each year from the hire date until the last day of their 4th year. At the 5 year mark, employees get 25 days until they reach their 9th year. At the 10 year mark, they receive 30 days off, the maximum amount of paid time off an employee can have. All vacation must be taken during the calendar year.

To implement these rules, we can modify the TotalVacation property so that it is encapsulated to calculate the vacation available in the getter. Encapsulation hides data as needed, and in this case, the encapsulation determines how much vacation any employee gets based on years or service.

Encapsulate field

We can replace the generated getter code with our business logic that determines the total vacation that an employee has along with the vacation remaining and used.

public double TotalVacation
    get {  
        var yearsOfService = (DateTime.Today.Year - this.HireDate.Year);
        switch (yearsOfService)
            case int n when (yearsOfService >= 0 && yearsOfService <= 5): 
                _totalVacation = 20; 
            case int n when (yearsOfService > 5 && yearsOfService < 10):
                _totalVacation = 25;
                _totalVacation = 30;
        return _totalVacation; }

The object hierarchy

We’ve learned from HR that we will be working with more than just full time employees for this app. We also need to create classes for part time and contract employees as well.

Many of these different kinds of employees will share most of the same properties. But we don’t want to make several classes that simply duplicate everything. Duplicated code is both difficult to maintain and error prone, since it’s impossible to remember where all the duplicated lines are.

This is where inheritance and the object hierarchy comes into play. These concepts in practice allow you to reuse code rather than duplicate it. So let’s refactor to create an Employee base class and derive other employee types such as FullTimeEmployee and PartTimeEmployee from that base class. The general Employee class contains everything but the vacation and benefits. Those are for full time employees only (sorry, part-timers!). Rider makes refactoring class hierarchies easy with the Extract to Superclass refactoring feature (Ctrl+Shift+R or Shift-Cmd-R for Mac).

Extract superclass

Now that we have an Employee base class and FullTimeEmployee derived class, we can create PartTimeEmployee and other classes that derive from the base Employee class. Use Alt+Enter to invoke the intention action.

Create derived type

In our scenario we’ve discovered that the business rules aren’t quite what we thought. Only full time employees can receive a bonus. So we need to move the Bonus property from the Employee base class to the FullTimeEmployee class.

Push members down

In .NET, all objects implicitly inherit from System.Object. That means you can override Object’s built in methods such as ToString, Equals, and GetHashCode as needed. In our case, it’s worthwhile to override the ToString method to return employee details. Use Alt+Enter then invoke the Generate Members intention.

Override tostring

Enforcing rules

In object oriented programming, one way to enforce rules is through interfaces, since they behave as a digital contract in which the implementer of the interface must implement all the members. Interface members are similar to clauses in a legal contract.

The HR department has a rule: all absences must be recorded for all employees. Since rules are rules, we’ll refactor to extract an interface (Ctrl+Shift+R/Shift-Cmd-R for Mac) to make sure all absences are recorded, as well as vacation deductions.

Extract interface

Alternatively, we could accomplish the same results by making the method abstract or virtual. Abstract members provide no implementation – that’s done in the derived classes. Virtual members have code in the base class but can be overridden when needed in derived classes.

In our example, instead of using an interface we may want to create a virtual LogAbsence method, so that employee absences can be logged specifically in the derived classes.

Override virtual methods

There are so many different ways to create object models that work well, and Rider has intention actions to help us regardless of the strategies we take.

Class organization and navigation

After completing some refactorings, we now have several classes and enums in the same file. To keep organized, they should go in their own separate files, named to match. While it’s not strictly part of creating an object model or object-oriented programming, it is important for readability and maintainability to have our classes organized nicely. We can move individual classes or have Rider move them all at once!

Organize classes

In addition to organizing classes, we must be able to navigate them to read or modify code. We can do this by using Rider’s Type Hierarchy feature to view which class inherits from which. Press Alt+Enter to invoke the intention, or use the shortcut Ctrl-E, H or Cmd+E, H on the Mac.

View model hierarchy

This article has demonstrated some of the many excellent features Rider has for building object-models that support business rules. So download Rider and start building robust object models today!


Subscribe to .NET Tools updates