C# Interfaces and Classes
By: Tom Chizek
Tags: C Sharp Programming, Software Design, Object Oriented, How To

Introduction

I have seen several videos and articles lately that propose the ideal that Object-Oriented Programming as a skill should be ignored, or at very least deemphasized. In many of these cases, what they were objecting to was the bad object, interface, or inheritance design, along with the objection that OOP is hard. Well, sure, it’s hard to get right, but so are programs. It’s easy as hell to write a piece of software that fulfills the obvious requirements for the average case. What is hard is writing software that also covers the unstated requirements for the edge cases, then test the edge cases to you know they are handled. These are the kind of problems that Object-Oriented Design and Programming were developed to solve.

Sure, applying OOP to the average apple, orange, and fruit class structure in every OOP book back to the beginning of OOP is overkill. But when you use OOD to breakdown a security system, factory, or building control system into component parts and build it up into useable objects with clean interfaces, you have something that you can extend and reuse.

In at least three cases, the author said they thought we should throw out the whole Object concept.

While the language, communication method, or rules for defining interfaces does not make object-oriented programming, language support makes the process easier. So, I will go over the basics of Object-Oriented Programming (OOP) using C# as the target language.

Object-Oriented programming is a mindset more than anything else. It is the idea of finding the natural components or “objects” that an application breaks down into. We then look for commonalities between those objects. Those commonalities are natural interfaces. These are externally visible interfaces from the object’s perspective but may or may not be exposed farther than the current application or library. These are the interfaces that make the engineer’s job easier by giving them a standard set of connection points to work with over the code's life.

The decision about exposing them beyond the application or library comes with experience and is usually the team lead’s decision in conjunction with the other team leads. On a one-person team, ask the question, are these functions useful generally in my projects? If so, put them into a personal NuGet package managed locally. If they are something new that you want to share with the world, put them up on NuGet. This step takes more effort and should be accompanied by better documentation. If you think it would be useful to the world, put it into a publicly accessible GitHub repository. If you are already using GitHub for your version control, this is as easy as making it publicly searchable.

Image by <a href=”https://pixabay.com/users/publicdomainpictures-14/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=2277">PublicDomainPictures</a> from <a href=”https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=2277">Pixabay</a>

Objects

This subject is where most articles or books on OOP start talking about fruit, animals, or cars. The problem with all of these things is that they are physical world objects. Because of this, the reader is biased to assume that all objects in an Object-Oriented design or application must be physical. This bias leads to the designer going through strange contortions to find a material equivalent to some software construct.

Don’t do this. If you start calling your network connections wires or your generic data objects books (unless they represent books, of course), you will confuse the next person to maintain your application. That person may end up being you. I have done this to myself.

Years ago, I got clever with the naming conventions of the database objects in an IoT application, naming them for the physical objects they represented rather than their function — fast forward two years when I had to go back in and add functionality. I had to figure out if the “Fan Controller,” “Fan,” or “Thermostat” object was the bit of code that stored the current control state of the Fan — it was none of those three. It was the “Duct” for some damn reason. Long story short, I ended up refactoring the code to a different set of objects that reflected the natural data and communication flows rather than the physical object naming and external view.

That’s rule one, your Objects are software constructs design and write them to be software, not for external interfaces. The external communication comes later and can reference any number of Objects you need to make it work. Don’t twist software objects into unusable forms to make them match a physical item.

The other thing to recognize about Objects is that while you don’t need programming language support to create them. It makes it easier for you as an engineer. While I have written OO applications in C, BASIC, PASCAL, and x86 Assembly, since it is unsupported in the language, it only takes one mistake to bring the entire application down. Trying to write OO in languages that don’t support it takes a mindset, discipline, and following a set of conventions to make it work. It depends on everyone who ever maintains or uses the application continuing that same set of conventions. It rarely works long term and can often lead to unmaintainable code if pushed to extremes.

That’s rule two, Object-Oriented is a mindset, not the language or syntax, but trying to force an OO design into a language that does not support it is asking for trouble. Use the right tools for the job. If you are working in a non-Object-Oriented environment, then bring the concepts but don’t shoehorn in bits that don’t work. Classes map nicely on top of structures with associated functions in C, but adding inheritance or interfaces breaks down quickly without additional support.

When designing and writing, your classes watch out for two typical mistakes that are easy to make.

The first is to make classes that do everything. I have seen this most often in people coming from languages with shared data spaces, where there is weak or no scoping of variables and data objects. They tend to be comfortable with having all of their data accessible to all of their code, so they write one overarching “class” that is basically a functional program. Often these are spread across multiple files, have private classes, structs, and enumerations inside the class rather than being at the namespace level. These are utter nightmares to support, even when you were the author, going back to fix a bug in one of these is an exercise in frustration because they are tangles of side-effects and unexpected interactions. Since there is no scope on class properties, change a property in a method, call a method that shouldn’t change that property. It may call something that does change the property, so when the original method finally gets to execute again, the value isn’t what is expected. When I first started in OOP, I made this mistake several times, and I have had to untangle other people’s messes of these several times since. If you ever had to support one of mine, I am sorry.

The second is classes that do nothing. A few years after I started programming using C++, I changed jobs. As part of my onboarding for my new company, the manager pointed me at a subsystem of the Airport Baggage Handling Sorter code running slow enough that it couldn’t sort bags even on a slow conveyor. When I started digging, I discovered that the person who wrote the subsystem had nested classes inside of classes, each with a separate inheritance tree. Remember, this was C++, so multiple inheritances are in play, making the structure more complex. Shortening a long involved story of software archeology, I found that every property was built up out of classes built up out of classes that inherited from classes that eventually resolved back to one or two fundamental numeric values of integer, double, or float. Some of these classes had fifteen or twenty properties, each a class with four or five properties made up of four of five of these classes, resolving down a minimum of five layers deep and a maximum of twenty layers deep. This structure wasn’t class design to simplify. It was class design for the pure joy of making classes because they could. I removed over 100,000 lines of code by replacing the nested inheritance with the value numeric types they represented and removing all of the support overrides that handled the math and comparison operations for those types. When this was done, we were unable to measure the time it took to calculate the bag’s destination after getting the barcode information. I don’t remember how long it took before that, but I remember that the timer measurements on either side of the call always returned the same ‘tick’ value. We were doing all of this back in the bad old days when a tick was measured in milliseconds (I think it was 10 ms), so not a great resolution by today’s measure, but at the time, that was fast.

Rule three, call it the goldilocks rule, classes shouldn’t be too big or too small. They should be just right. Figuring out what is just right is an experience thing. Still, in general, if you have to look more than four or five levels of inheritance deep, you may be on the small side. If you have fewer than three classes in an application you expect to sell or publish professionally, you are probably on the big side. On the other hand, sometimes that’s just how the objects break down, and there is nothing against big or small classes if they are natural for your application.

Image by <a href=”https://pixabay.com/users/geralt-9301/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3139197">Gerd Altmann</a> from <a href=”https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3139197">Pixabay</a>

Interfaces

Interfaces are a badly overloaded word in software engineering. Sometimes it feels like we use the phrase Interface when we don’t know what else to call something. It is essential to keep the difference between internal and external interfaces solid in your mind and design when working with Object-Oriented Design and Programming.

If you are sure that interfaces and classes are only used internal to a single namespace, you can make the entire inheritance structure internal. However, at that point, think hard about this set of interfaces and classes. Is it useful enough to break out into a separate namespace with a public interface so other parts of the application can use it? Is it needed outside a specific class? If not, should it be a subordinate class rather than an entire inheritance tree? Is it useful enough to break it out into your NuGet library of useful classes? You do have one of those, right?

Rule one of interfaces is only making public interfaces that make sense for an external program to use. This rule means thinking about your interfaces and classes before making them public. I realize this is painful and involves forethought, but making everything public by default asks for security breaches. In the current security environment, we can’t just depend on external security. We need to think about what individual programs and even classes impact on security is going to be.

Another thing to think about for interfaces is what parts need to be writable. Do users of the interface need to write every property that you have exposed? If some of them do, then would it be better to break the interface into two parts? Or add an internal setter on the implementation of the interface? There are multiple ways to solve this possible issue. Not all of them make sense for any specific application, and only you can decide what makes sense for your application. But think about the level of access needed before blindly making all properties read-write and public.

In C#, make use of private and internal setters on properties in classes by not making setters part of the interface unless you are sure that the property is something that an external user should be setting.

Something to note here, if you are using the Microsoft implementation of Entity Framework, you don’t have any choice but to make any properties that are to be stored in the database (i.e., part of the Entity) public and writeable. So, that is a limitation on the ability of some classes to have read-only properties. Entity classes can’t have read-only properties that are stored in the Database.

Rule two of interfaces think before making properties read-write.

My most basic rule of interfaces is to extend them with interfaces or classes that build on their functionality and avoid changing the interface, if at all possible. Changing an interface in C# implies that you have missed something basic about your application. I have had to do this many times in my career, and it is never a good sign for the project unless it happens early in the design or prototyping phases of the project.

Here is the baseline interface that all of the objects in a project I am working on implement. This project was a case that I noticed a commonality in multiple interfaces and classes during the prototyping stage, so I broke this interface out to create:

public interface IStarMapObject

{

[Key]

[Column(Order = 1)]

Guid Id { get; set; }

string Name { get; set; }

DateTime Created { get; set; }

DateTime LastUpdated { get; set; } int Hasher();

string ToString();

string ToString(string format);

string ToString(string format, IFormatProvider formatProvider);

string ToShortString();

}

Since this is an Entity Framework project and all of the classes that implement this interface are Entity objects, all of the properties must implement a public setter.

An interface that extends the IStarMapObject is IAstronomicalObject, which looks like the following:

public interface IAstronomicalObject : IStarMapObject

{

[ForeignKey("StarSystem")]

[Column("StarSystem")]

Guid? StarSystemId { get; set; }

StarSystem StarSystem { get; }

[BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "OrbitalDistance")]

public Length OrbitalDistance { get; } [BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "OrbitalVelocity")]

Speed OrbitalVelocity { get; } [BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "Mass")]

Mass Mass { get; } [BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "Radius")]

Length Radius { get;} [BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "Gravity")]

Acceleration Gravity { get; } bool Equals(IAstronomicalObject ao);

}

So, any classes that implement IAstronomicalObject must also implement IStarMapObject plus the new properties. You might note that there are properties without public setters in this interface. That is because all of these are either not available as part of the interface only part of the objects that implement the interface or are calculated values (Gravity and Orbital Velocity)

Rule three, extend your interfaces. Don’t change them. For example, changing the type of StarSystemId after the fact would be bad news. It could cause unexpected problems with your application and the database and any other applications that depend on this interface or the database built based on this interface.

Note that one way to build a database using Entity Framework is to write the interfaces, classes, and entity definitions in code first and then run the EF database generator to create the database. Changing column types is possible but very painful. I would recommend never doing this if your database is live as it can cause data and weekend loss.

Abstract and Virtual Classes

I often use the general term of Base classes for these two types of classes for the concept of a class that is intended to serve as the parent class of other classes. The difference is that while a virtual class has no differentiator in the class definition and implements virtual methods or properties that can be overridden by the child class but do not have to be. An abstract class is declared abstract in the class definition and defines some properties or methods as abstract, defining only the signature. The implementation of the method or property must be defined in the inheriting class.

Virtual classes may not be created as concrete objects. Meaning that you can’t call “new” for these classes. They can only be used as declarations of variables or properties, much like Interfaces. The matching abstract class for the IStarMapObject interface above is:

abstract public class StarMapObject : IStarMapObject

{ #region properties

[Key]

[BindProperty]

abstract public Guid Id { get; set; }

[BindProperty]

public string Name { get; set; }

[BindProperty]

public DateTime Created { get; set; } [DisplayName("Last Updated")]

[BindProperty]

public DateTime LastUpdated { get; set; }

#endregion #region constructors

public StarMapObject(Guid id, string name, DateTime c, DateTime u)

{

Id = id;

Name = name;

Created = c;

LastUpdated = u;

} public StarMapObject(Guid id, string name) :

this(id, name, DateTime.Now, DateTime.Now) { } public StarMapObject() :

this(Guid.Empty, string.Empty, DateTime.MinValue, DateTime.MinValue) { }

#endregion #region public instance methods

virtual public bool CheckEquality(object obj)

{

if (obj == null)

{

return false;

}

if (obj.GetType() == typeof(StarMapObject) || obj is IStarMapObject)

{

return Equals(obj as StarMapObject);

}

return base.Equals(obj);

} virtual public bool Equals(IStarMapObject smo)

{

if (smo == null)

{

return false;

}

bool result = Id.Equals(smo.Id) &&

Name.Equals(smo.Name);

return result;

} virtual public int Hasher()

{

int hash = Id.GetHashCode();

hash = HashCodeHelper.CombineHashCodes(hash, Name.GetHashCode());

return hash;

}

public override string ToString()

{

return ToString("g", CultureInfo.CurrentCulture);

}

virtual public string ToString(string format)

{

return ToString(format, CultureInfo.CurrentCulture);

}

virtual public string ToString(string format, IFormatProvider formatProvider)

{

StringBuilder sb = new StringBuilder();

string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator;

sb.Append($"{Id}{separator} {Name}{separator} {Created}");

return sb.ToString(); }

virtual public string ToShortString()

{

return $"{Name}";

}

#endregion }

There are a couple of things that I feel like I should go over with this class. First, I deliberately forced it to be abstract by making the Id property abstract. Functionally there is no good reason to do this except that I do not want anyone to be able to instantiate instances of this class. Second, I still made constructors. These implementations are a good habit to get into that I will cover when I show some of the classes that inherit from StarMapObject. Last, I implement but make virtual all of the methods for the class. Again there is a reason for this that will be apparent as we look at child classes.

So, the next layer is another abstract class, the AstronomicalObject. I was thinking about only giving a partial view of the class, but I decided that it didn’t make sense, so here it is in all its glory.

abstract public class AstronomicalObject : StarMapObject,

IAstronomicalObject

{

#region Properties

[ForeignKey("System")]

[BindProperty]

abstract public Guid? StarSystemId { get; set; }

abstract public StarSystem StarSystem { get; set; } [DisplayName("Orbital Distance")]

[BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "OrbitalDistance")]

public Length OrbitalDistance { get; internal set; }

[BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "Mass")]

public Mass Mass { get; set; }

[BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "Radius")]

public Length Radius { get; set; } [DisplayName("Orbital Velocity")]

virtual public Speed OrbitalVelocity

{

get

{

Mass m = new Mass();

if (StarSystem != null)

m = StarSystem.Mass;

Speed retValue = m.CalculateOrbitalVeclocity(OrbitalDistance); return retValue;

}

} [BindProperty(BinderType = typeof(QuantityValueEntityBinder), Name = "Gravity")]

public Acceleration Gravity

{

get

{

return Mass.Gravity(Radius);

}

}

#endregion #region Constructors

public AstronomicalObject(Guid id, string name, DateTime c, DateTime u,

Mass m, Length r) :

base(id, name, c, u)

{

Mass = m;

Radius = r;

} public AstronomicalObject(AstronomicalObject a) :

this(a.Id, a.Name, a.Created, a.LastUpdated, a.Mass, a.Radius) { } public AstronomicalObject(Guid id, string name, Mass m, Length r) :

this(id, name, DateTime.Now, DateTime.Now, m, r) { } public AstronomicalObject() :

this(Guid.Empty, string.Empty, DateTime.MinValue, DateTime.MinValue,

Mass.Zero, Length.Zero) { }

#endregion #region public methods

public override bool CheckEquality(object obj)

{

if (obj == null)

{

return false;

}

if (obj.GetType() == typeof(AstronomicalObject) ||

obj is IAstronomicalObject)

{

AstronomicalObject ao = obj as AstronomicalObject;

return Equals(ao);

}

return base.CheckEquality(obj); } virtual public bool Equals(IAstronomicalObject ao)

{

if (ao == null)

{

return false;

}

bool result = base.Equals(ao) &&

Equals(StarSystem, ao.StarSystem) &&

Equals(Mass, ao.Mass) &&

Equals(Radius, ao.Radius) &&

Equals(OrbitalDistance, ao.OrbitalDistance);



return result;

} virtual public new int Hasher()

{

int hash = base.Hasher();

hash = HashCodeHelper.CombineHashCodes(hash, Mass.GetHashCode());

hash = HashCodeHelper.CombineHashCodes(hash, Radius.GetHashCode());

if (StarSystem != null)

hash = HashCodeHelper.CombineHashCodes(hash, StarSystem.Hasher());

hash = HashCodeHelper.CombineHashCodes(hash, OrbitalDistance.GetHashCode());

return hash;

} public override string ToString(string format, IFormatProvider formatProvider)

{

StringBuilder sb = new StringBuilder();

string separator = NumberFormatInfo

.GetInstance(formatProvider)

.NumberGroupSeparator;

sb.Append($"{base.ToString(format, formatProvider)}" +

$"{separator} {Mass.ToString(format, formatProvider)}{separator} " +

$"{Radius.ToString(format, formatProvider)}{separator} " +

$"{Gravity.ToString(format, formatProvider)}{separator} " +

$"{OrbitalDistance}{separator} " +

$"{OrbitalVelocity.ToString(format, formatProvider)}{separator} " +

$"{StarSystem.Name}");

return sb.ToString(); }

public override string ToShortString()

{

return $"{base.ToShortString()} Gravity:{Gravity}"; }

#endregion

}

The important thing about this class is the places where I call “base.<some method>”. For example, in Hasher, I start the hash with base.Hasher() this way, I am not duplicating the code written in the base class. This pattern works all the way up the inheritance. When a class that inherits from AstronomicalObject implements its version of Hasher, adding the properties that need to be hashed, it will start the same way by assigning base.Hasher() to hash. There are multiple places in this class where I use this functionality, and it is one of the tools that make reuse of code much more straightforward in C#.

The next type of class I will discuss is the core of Object-Oriented programming, the concrete class. The classes that you create, that usually implement the majority of the code in an OOP application. These are the classes that implement the interfaces, abstract, and virtual classes.

Classes

Concrete classes can often have a separate interface, or they can implement a set of other interfaces. It depends on how your project naturally breaks down into interfaces, base classes, and classes.

To talk about the next class, I need to drop back and talk about the interface for it. Planet is a relatively simple Astronomical Object. Its interface looks like:

public interface IPlanet : IAstronomicalObject

{

int OrbitalOrder { get; set; } [Column("AtmosphereId")]

Guid? AtmosphereId { get; set; }

Atmosphere Atmosphere { get; set; }

[BindProperty(BinderType = typeof(QuantityValueEntityBinder),

Name = "Temperature")]

Temperature Temperature { get; set; }

bool Habitable { get; } // IPlanet objects all relative to the StarSystem they are associated with

// for these purposes the Global "zero point" for a given set of planets and

// location it is associated with.

[BindProperty(BinderType = typeof(SupportEntityBinder),

Name = "Location", SupportsGet = true)]

Point3D Location { get; set; }

}

Since this is the first concrete class, I will discuss a few things that are different. First, all objects in C# inherit from object. Because of this, there are a few methods that can and should be overridden for complete objects. The first one is GetHashCode(), which returns the hash of the object used by any of the dictionary, tree, hash-sort, or other algorithms that use has. The default hash is okay for simple objects, but it is worth overriding when you implement more than a few properties. Similarly, the Equals(object obj) method is the generic comparison used for comparing any unknown object to your object. The base object class gives a basic implementation of this, but again, anything more complex than a few properties, it is worth overriding this and putting your own custom method.

Other than that, the overrides follow the same rules as from StarMapObject to AstronomicalObject. If Planet needs a different variant than the virtual implementation from AstronomicalObject, the method or property is overridden. The Planet class looks like:

public class Planet : AstronomicalObject, IPlanet, IAstronomicalObject, IStarMapObject

{

#region Properties

[Key]

public override Guid Id { get; set; }

[ForeignKey("StarSystem")]

public override Guid? StarSystemId { get; set; }

public override StarSystem StarSystem { get; set; }

[BindProperty(BinderType = typeof(QuantityValueEntityBinder), Name = "Mass")]

public override Mass Mass { get; set; } [BindProperty(BinderType = typeof(QuantityValueEntityBinder), Name = "Radius")]

public override Length Radius { get; set; }

public int OrbitalOrder { get; set; } [ForeignKey("Atmosphere")]

public Guid? AtmosphereId { get; set; }

public Atmosphere Atmosphere { get; set; } [BindProperty(BinderType = typeof(QuantityValueEntityBinder), Name = "Temperature")]

public Temperature Temperature { get; set; } public bool Habitable

{

get

{

if (Atmosphere.AtmosphereComposition

.Equals(PlanetaryAtmosphereComposition.Breathable) ||

Atmosphere.AtmosphereComposition

.Equals(PlanetaryAtmosphereComposition.Normal) ||

Atmosphere.AtmosphereComposition

.Equals(PlanetaryAtmosphereComposition.Controlable))

{

if (Atmosphere.AtmospherePrimary

.Equals(PlanetaryAtmosphereDensity.Thin) ||

Atmosphere.AtmospherePrimary

.Equals(PlanetaryAtmosphereDensity.Normal) ||

Atmosphere.AtmospherePrimary

.Equals(PlanetaryAtmosphereDensity.Heavy))

{

double tempInDegreesC = Temperature.DegreesCelsius;

if (tempInDegreesC > -60 && tempInDegreesC < 50)

{

return true;

}

}

}

return false;

}

} // IPlanet objects all relative to the StarSystem they are associated with

// for these purposes the Global "zero point" for a given set of planets and

// location it is associated with.

[BindProperty(BinderType = typeof(SupportEntityBinder),

Name = "Location", SupportsGet = true)]

public Point3D Location { get; set; }

#endregion #region Constructors

public Planet(Guid id, string name, int order, DateTime c,

DateTime updated, StarSystem o, Mass m, Length r,

Length od, IAtmosphere a, Temperature t, Point3D loc )

{ Id = id;

Name = name;

OrbitalOrder = order;

Created = c;

LastUpdated = updated;

StarSystem = o;

StarSystemId = Guid.Empty;

if( o != null)

{

StarSystemId = o.Id;

} Mass = m;

Radius = r;

OrbitalDistance = od;

Atmosphere = a as Atmosphere;

AtmosphereId = Guid.Empty;

if( a != null )

{

AtmosphereId = a.Id;

}

Temperature = t;

Location = loc;

} public Planet(Guid id, string name, int order, DateTime c, DateTime u,

StarSystem o, string m, string r, string od, IAtmosphere a,

string t, string loc)

: this(id, name, order, c, u, o, Mass.Parse(m), Length.Parse(r),

Length.Parse(od), a, Temperature.Parse(t), Point3D.Parse(loc)) { } public Planet(Guid id, string name, int order, Mass m, Length r,

IAtmosphere a, Temperature t)

: this(id, name, order, DateTime.Now, DateTime.Now, StarSystem.Empty,

m, r, Length.Zero, a, t, Point3D.Zero ) { } public Planet(Guid id, int order, DateTime c, DateTime u, Mass m, Length r,

IAtmosphere o, Temperature t)

: this(id, $"planet:{id}", order, c, u, StarSystem.Empty, m, r,

Length.Zero, o, t, Point3D.Zero ) { } public Planet(string name, int order, StarSystem o, Mass m, Length r,

Length od, IAtmosphere a, Temperature t, Point3D loc )

: this(Guid.NewGuid(), name, order, DateTime.Now, DateTime.Now,

o, m, r, od, a, t, loc ) { } /// <summary>

/// Construct a zero planet, at the center of everything, unmoving,

/// no atmosphere, no mass, no radius...

/// I guess it is a nothing?

/// </summary>

public Planet() : this(Guid.Empty, string.Empty, 0, DateTime.MinValue,

DateTime.MinValue, StarSystem.Empty, Mass.Zero,

Length.Zero, Length.Zero, Atmosphere.Empty,

Temperature.Zero, Point3D.Zero) { } public Planet(Planet p)

: this(p.Id, p.Name, p.OrbitalOrder, p.Created, p.LastUpdated,

p.StarSystem, p.Mass, p.Radius, p.OrbitalDistance,

p.Atmosphere, p.Temperature, p.Location) {}

#endregion #region Public Instance overrides

/// <summary>

/// override the base object Equals method to handle the

/// details of the Planet object.

/// </summary>

/// <param name="obj">Object to check for equality with "this"</param>

/// <returns></returns>

public override bool Equals(object obj)

{

if (obj == null)

{

return false;

}

if (obj.GetType() == typeof(Planet) || obj is IPlanet)

{

return Equals(obj as IPlanet);

} return base.CheckEquality(obj);

} /// <summary>

/// override the base object Equals method to handle the details

/// of the Planet object.

/// </summary>

/// <param name="p">IPlanet object to compare</param>

/// <returns></returns>

public bool Equals(IPlanet p)

{

if (p == null)

{

return false;

}

bool result = base.Equals(p) &&

OrbitalOrder.Equals(p.OrbitalOrder) &&

Atmosphere.Equals(p.Atmosphere) &&

Temperature.Equals(p.Temperature);

return result;

} /// <summary>

/// Build a better hash code than the basic object hash.

/// </summary>

/// <returns></returns>

public override int Hasher()

{

int hash = base.Hasher();

hash = HashCodeHelper.CombineHashCodes(hash, OrbitalOrder.GetHashCode());

hash = HashCodeHelper.CombineHashCodes(hash, Atmosphere.Hasher());

hash = HashCodeHelper.CombineHashCodes(hash, Temperature.GetHashCode());

return hash;

} public override int GetHashCode()

{

return Hasher();

} /// <summary>

/// Returns a String representing this Atmosphere instance,

/// using the specified format to format individual elements

/// and the given IFormatProvider.

/// </summary>

/// <param name="format">The format of individual elements.</param>

/// <param name="formatProvider">The format provider to use when formatting elements.</param>

/// <returns>The string representation.</returns>

public override string ToString(string format, IFormatProvider formatProvider)

{

StringBuilder sb = new StringBuilder();

string separator = NumberFormatInfo.GetInstance(formatProvider)

.NumberGroupSeparator;

sb.Append('<');

sb.Append($"{base.ToString(format, formatProvider)}{separator} " +

$"{OrbitalOrder}{separator} " +

$"{Atmosphere.ToString(format, formatProvider)}{separator} " +

$"{Temperature.ToString(format, formatProvider)}");

sb.Append('>');

return sb.ToString();

} public override string ToShortString()

{

return $"{base.ToShortString()} Atmosphere:{Atmosphere.ToShortString()} {Temperature}";

} #endregion #region public static methods public static bool operator ==(Planet left, Planet right)

{

if (Equals(left, null))

{

return Equals(left, right);

}

return left.Equals(right);

}

public static bool operator !=(Planet left, Planet right)

{

if (Equals(left, null))

{

return !Equals(left, right);

}

return !left.Equals(right);

} public static Planet Empty { get; } = new Planet();

public static Planet Earth { get; } =

new Planet(Guid.Empty, "Earth", 3,

DateTime.Now, DateTime.Now,

StarSystem.Sol,

Constants.EarthMass,

Constants.EarthRadius,

Constants.AU, Atmosphere.Standard,

new Temperature(21, UnitsNet.Units

.TemperatureUnit.DegreeCelsius),

new Point3D(

x: new Length(0, UnitsNet.Units

.LengthUnit.AstronomicalUnit),

y: GeneralCalculations

.FindOrbitalYLocation(

x: new Length(0,

UnitsNet.Units

.LengthUnit.AstronomicalUnit),

r: Constants.AU),



z: new Length(0, UnitsNet.Units

.LengthUnit.AstronomicalUnit)));

#endregion }

There are a few things to note at the end of the class. The operator overrides for the class to handle ‘==’ and ‘!=’, making some of the class’s uses more natural. There are also two static instances of planets for use and reference, the Empty Planet and Earth. These are built out because both are reference points for the planet type.

Conclusion

Object-Oriented Design and Programming isn’t perfect. It is complex. It is challenging to get correct. It is not a particularly natural way to think. Not every problem obviously breaks down into natural classes. These are some of the arguments against OOD and OOP. To these, I say yup, sure enough. My answer is, what is better? Seriously for full-sized professional enterprise or commercial applications, OO is used because it works better than the alternatives. We have tried alternatives, oh hell, we have tried alternatives. Every couple of years since 1990, there is a programming fad that will replace Object-Oriented Design and Programming. Management, Project Managers, and well-paid Consulting Firms jump on the bandwagon and roll out the flavor of the year. Companies dump hundreds of thousands or millions of dollars into switching over to the new thing, bring on the experts, and start transitioning projects. Sometimes one project finishes years late, with half the functionality at triple the cost but usually, the projects crash and burn, and IT staff have to rush in to patch the new functionality into the old code-base. These diversions are why I have seen so many 15–30 year old first or second-generation Object-Oriented programs still running in VB.Net, C#2.0, C++ 7 or earlier, because the budget and schedule that should have paid to update them went to the ‘new fad of the year’ which failed. So the old OO code marches on, supported by people who know for a fact that it would be cheaper to redesign and rewrite it in modern OO tools and standards, but there isn’t money for ‘old fashioned’ OO because there is this new thing that will replace it all. So again, money gets dropped into the fad of the year, and the OO code slogs on.

Do you think I am exaggerating? Nope, I have made a living these last five years doing exactly this. In one case, hired back as a contractor at a company that I left in 2008, I ended up fixing bugs in code I wrote in 2006 on servers that I wrote Purchase orders for when I was the Department’s Manager in 2006. At the same time, they had gone through three ‘latest newest things’ but were back on Java and C# code written a decade or more ago because it works. I have a pile of similar stories. That one is just more painful because I saw the before and after results. But I have been hired for short term contracts to “just get this application working so we can use it until X is done.” When talking to people I know at the company a year later, X was canceled, and the patch job I did on the application will be running for the foreseeable future. Isn’t software engineering fun?

Everyone needs to stop looking for a silver bullet. There is no such thing, and looking for one makes the management types think that the next magic spell can solve their software problems. Who knows, maybe someday we will have AI that can code circles around all of us, but until we do stop overpromising, you are hurting everyone.

Good luck, and have fun.