Develpreneur: Become a Better Developer and Entrepreneur

Rob Broadhead
undefined
Mar 22, 2021 • 16min

Cohesion - A Big Object Approach To Design

This episode focuses on cohesion and the idea of using fewer large objects to solve a problem.  This approach is often considered the opposite of using more smaller objects.  We will find out that there are ways to combine these two different approaches as a best-fit method. Cohesion Strengths We have already defined and provided a summary of cohesion.  Therefore, we can dive right into some specifics and strengths of this approach.  I find simplicity to be one of the most apparent strengths of these solutions.  While it provides a sort of "junk-drawer" approach in some cases, it also means we do not spend time deciding what goes where.  When done properly, we can see this approach as having all of the important things you are working on sitting on your desk.  Nothing is filed away.  Thus, access is quick and easy. The Performance Aspect The lack of a need to "find things in your file cabinet" provides us efficiencies.  In the code context, we have fewer abstraction layers to dig through for a value or function.  Everything is readily available to the code, and permissions are a non-issue.  This configuration provides for improved performance.  Think of it as the best way to get something done is to do it yourself.  Some developers see it as getting a task done quickly rather than creating classes to break the task into smaller pieces. Power In Simplicity There is power, as well as beauty, in simplicity.  The product lines of Apple are a commercial example of this concept.  We can remove a lot of time and decision points by limiting options.  In the cohesion context, this comes from us knowing where a property or method belongs.  We also spend less time worrying about what we should be able to see an attribute or method.  When we take this to an extreme, we can see a near return to the old single file, functional approach to designing a software solution.  We get to focus on functions over where they "live."  When the solution is small, this all will make complete sense.  Why complicate a solution needlessly?
undefined
Mar 19, 2021 • 15min

Cohesion and Coupling in Object-Oriented Design

Cohesion and coupling are options that are often discussed in object-oriented design circles.  They are two ways to approach a solution.  On the one hand, the solution can be a larger, self-contained approach.  On the other hand, the solution can be a core that uses several "helpers" to achieve the objective(s).  These are important options for us to consider as part of the overall OOD discussion.  In this episode, we start with an overview of these common paths. Cohesion Defined There are many ways to think about Cohesion.  However, I find it best to start with a common form of definition like the one found on Wikipedia. In computer programming, cohesion refers to the degree to which the elements inside a module belong together. In one sense, it is a measure of the strength of relationship between the methods and data of a class and some unifying purpose or concept served by that class. In another sense, it is a measure of the strength of relationship between the class's methods and data themselves. While we will not cover it at this point, note that there is an idea of a relationship among properties and methods in a class.  This definition provides some foreshadowing in how we decide what method belongs where. Coupling Defined The ideas around coupling are much like cohesion.  Therefore, let's start again with a simple definition like the one found on Wikipedia. In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules. The key concept here is found in the mention of interdependence between modules.  It is a different focus from the inside of the module reference in our definition of cohesion. Cohesion and Coupling - Finding Their Place These two approaches are often seen as opposites.  Thus you take one approach or the other in your design.  However, that is not required.  There can be some large objects with little coupling or large objects that are rather cohesive (in general), yet they have a substantial number of helper objects.  These patterns of design do bring up the idea of consistency across a solution.  On the other hand, they provide ways to handle different problems within a system in the best manner for that problem. The best way to look at these two options is to understand the strengths and weaknesses of each.  Then we can find the best tool for the situation and expand our ways to solve each problem on its own.
undefined
Mar 17, 2021 • 14min

Property Design In An Object-Oriented System

One of the most prevalent topics of consideration in an object-oriented system is property design.  We can keep them simple or create more classes.  The first approach is simpler and faster to implement.  However, we can use objects to provide hooks we extensibility and scaling out our features.  There are trade-offs to be aware of, but we can use some guidelines to shorten the design process. Property Design Considerations For purposes of this episode, we will look at two paths available.  We can use a simple type (native) or use a class to define a property.  While we can also utilize collections as properties, those still boil down to one of these two paths.  You will either have a collection of native values or object instances. A Software Upgrade A primary benefit of the object approach for a property is that it allows us to upgrade or "power-up" a class.  A simple example of this is a greeting for a class.  We can leave it as a string, or we can provide a greeting object.  That object could start by displaying a greeting in English.  When we want to upgrade the getting, we can add support for other languages or even an interactive greeting object. The Plugin Approach The above example can be viewed as a plugin.  We can use this approach to provide a "quick and dirty" for the initial release and then improve it later.  This pattern is excellent in an Agile environment in particular.  We can build out functionality in one class.  Then, we can inherit and extend that class later.  The original features and functions are still available for instances built in the prior version.  Yet, we can use the new approach for new instances.  We get scalability along with stability.  We have designed our solution for growth and flexibility.  These are essential traits for an agile system.
undefined
Mar 15, 2021 • 17min

Class Relationships - When Has-A Shows a Relation

Our language can blur the distinctions of class relationships in designing our solution.  The "has-a" determination is often confused by how the English language can translate those words.  We also have to keep a context for assigning a class.  Likewise, reality allows for data to be multiple classes in some cases, so we need to examine how those might be a hierarchy of some sort. Class Relationships - "Have" As A Pointer The confusing aspect of the English language is that we can "have" a relationship and a property.  For example, you have an age, and you have a parent.  The age is a property of being you while the parent is a relationship.  This language blurring of properties can be confusing to those new to the idea of object-oriented design. Does It Stand On Its Own? The way to look at these different aspects with the same name is whether the target is itself an object.  That is, can it stand on its own apart from the "owning" class.  In our prior example, your age has no meaning apart from you.  That age is not something you can give to someone else.  Others have the same value for their age, but it is not the same age we are referring to.  Your parent exists outside of you, and others can have that same parent. The Impact Of Change In the software world, it may help to look at what happens when the target property changes.  Does it impact any other objects?  If I change your name (a property you have), that only impacts you.  It does not change anyone else's name.  If your car changes, that impacts you and anyone else that links to that car.  This is one of those data facets we see all the time.  Do we copy a value, or can we have multiple pointers to it?
undefined
Mar 12, 2021 • 16min

Is A and Has A Concepts in Object-Oriented Design

The ideas of "is a" and "has a" are often discussed as part of object-oriented design.  These concepts may seem simple and obvious.  However, they can often be confused, and complex systems can blur the lines between them.  We start a multi-part episode going over these concepts focused on how "is a" relationships work. Is A and Has A Defined These concepts boil down to simple grammar.  For two objects, A and B, here are the options. A is a B if A has all of the traits of B.  For example, a poodle is a dog because a poodle has the traits we equate to a dog.  A dog is not a poodle, as some dogs have traits that are not shared with a poodle. A has a B when the entirety of B exists within A.  A dog has a tail.  The tail does not exist on its own away from the dog.  More simply, a car has a color.  In these relationships, multiple things can have the B part of the relationship. One Way Relationships For our purposes, these are one-way relationships.  When A is a B, B is not an A.  Likewise, when A has a B, B does not have an A.  The color does not have a car; the tail does not have a dog.  These definitions can seem to break when we replace names with instances.  A father can have a son, and a son can have a father, but that is not a definition of contained properties.  This example would be a relationship or pointers and not attributes that are contained.  You have a height no matter what.  You may not have a father if he passes away or something like that.  As you can see already, grammar can confuse the situation, so we will continue to dig into these concepts and applying them.
undefined
Mar 10, 2021 • 16min

Granular Interfaces - How Much OO Is Practical?

The ideas of cohesion and coupling point us to paths that either place functionality in smaller or larger classes.  We discuss granular interfaces in this episode as an introduction to those "right-sizing" discussions.  Not all OO designs are created equal. Granular Interfaces - Bricks or Sand The best example I have come across in considering how to approach our design's granularity is comparing sand to bricks.  We can construct a building by starting from sand, making blocks of concrete, and then moving on to the structure.  However, it is easier when we can start from the blocks or bricks. The Lego Example We can similarly look at the children's toy Lego bricks.  There are various sizes of bricks available.  Therefore, we can build almost any structure we can imagine.  The challenge is that we can build it out of combinations of larger bricks or work only with the one-by-one size bricks.  The faster path will be to use larger bricks.  The trade-off is that there are fewer customizations as we move to larger blocks. We see this in software design as well.  The more granular our approach, the easier it is to customize.   On the other hand, we do not want to a la carte our users into confusion.  They should not have to hand-pick every single method or feature.  Instead, we need to provide properly designed granular interfaces that group features that are best handled as a group. Grouping Methods While all assumptions will not hold in 100% of the cases, we can make assumptions to reduce our application's complexity.  For example, we can assume that a need to save a class includes a need to load.  Thus, we can take the CRUD functions and group them.  In business instances, we will see a lot of the same natural groups.  A customer contact feature will need to provide ways to create a customer, contact them, and track contact history.  Therefore, a customer interface is naturally going to be easier to use if it includes all of those methods.  These also help remind the developer (when using an interface) of functionality that should probably be implemented.  At an application level, we can think of features like security, registration, help, and session management.  These are not always needed.  However, it is worth our while to intentionally determine whether our application can skip one or more of those.
undefined
Mar 8, 2021 • 15min

Inheritance - Polymorphism In A Hierarchical Manner

We switch gears in this episode and start to look at inheritance.  This is a core feature of object-oriented design and the most recognizable attribute.  Child classes are utilized through polymorphic support.  Thus, we have a natural transition into this popular usage of object-oriented solutions. A Lowest Common Denominator We have discussed the idea of building on methods where possible.  When we do, we are creating a lowest common denominator approach to functionality.  This is what makes a system logical in its design.  Whenever a user (developer) uses a method or attribute there are certain things they can expect that are included. For example, when I have "save" methods in classes, the user expects the class supports persistence.  They also will expect a corresponding "load" is going to be available in that class.  It is reasonable for the user to expect that a "save" followed by a "load" will result in the before/after instances being identical. Extending Through Inheritance We have the aforementioned set of expectations as developers.  Those lead us to look at a hierarchy as something that grows as it goes.  That means we should extend functionality as we inherit.  We should avoid rewriting what the parent does.  While we can change or even block behavior in child classes, that is rarely a good design.  It is highly frustrating for a developer to have a method available to the parent that is no longer relative (rewritten or unavailable) further down the chain. Think of each step in the hierarchy as a way to build on the parent features.  The base class supplies a foundation.  The child classes add to that foundation without impacting what has been built in the prior layers.  This approach will help your hierarchy help the developers that use it.
undefined
Mar 5, 2021 • 15min

Interfaces - An Object-Oriented Contract For Usage

This episode, we look at interfaces.  These are not supported by all languages.  However, when they are supported, they are very useful.  The interface specification allows us to provide language-based constraints for our method signatures.  It also is an excellent way to enforce consistancy. Interfaces As A Tool For Consistancy The most important result of using interfaces is that they enforce a consistant way to communicate with developers.  Any class that uses one of these items will be forced to comply with the defined signatures.  The language compiler or validations will let the developer know when they are not following the rules.  While there might be empty methods allowed, the developer must specifically handle the method and return an empty result. Common Examples The typical methods and functions we have mentioened throughout this season are going to often end up in an interface.  We see this in frameworks where there are numerous helper objects in the system.  For example, there may be interfaces to save, print, load, export, or compare instances.  These consistant interfaces allow us to treat a broad range of disparate objects the same. One such example would be sorting a list.  We all can imagine sorting a list of names.  On the other hand, how would we sort a list of cars?  We can implement a compare interface in the car class.  That can provide a process for comparing two cars and deciding an order to them.  Once that has been implemented, we can display a list of cars and provide a sort option.
undefined
Mar 3, 2021 • 13min

Flexibility in OOP - Build in hooks for change

One of the essential concepts to understand is flexibility in OOP.  A good design requires the ability to extend it.  There are ways to do this.  However, they require us to incorporate mechanisms for validation and growth.  That means we need to find a balance between the core to our functionality and what can be altered as an enhancement. Flexibility in OOP - Many Options There are multiple ways to build flexibility into your object-oriented design.  We can use parameters that determine functionality, check for class types, or even plugin commands.  These are critical for an effective design as we want to keep our solution flexible enough to grow.  Anything rigid is also going to be fragile to some extent.  Therefore, we should build a solution that bends without breaking.  Likewise, we want to minimize assumptions.  We can never be sure where developers will want to take our code. Strict Language One thing to keep in mind with these hooks that you supply is including restrictions.  For example, we can use enumerated types and exceptions to ensure users stick within reason for parameters.  This approach allows us to leave room for growth while still requiring code to work within the given framework.  We can have a print that takes a parameter for output type that only works when the type is within a collection to enumeration.  Then the set of valid options can be extended as needed. Ground Rules The simplest way to think about this need for some flexibility, but not too much, is as ground rules.  There are certain things your code will expect to be available.  Thus, exceptions will be thrown when those requirements are not met.
undefined
Mar 1, 2021 • 15min

Code Consistency - Critical For Practical Polymorphism

One of the challenges of good polymorphic design is code consistency.  We are building a way to communicate with developers.  Therefore, our language or syntax needs to be easy to understand.  Likewise, we need to set and meet expectations properly. Code Consistency In Results The most common error I find in this area of coding is found in the values returned.  When we stick to native types and numbers, it tends to be easy enough to stay consistent.  However, strings and structures lend themselves to issues. For example, an address can have many properties and formats.  We may be able to start with an output of: "address line, city, state zip."  However, addresses vary.  What happens when we want to add in a country or a different zip code format?  The best approach to take in these cases is to extend as opposed to convert. Lowest Common Denominator This thought process leads us to a focus on the lowest common denominator among classes and polymorphic methods.  We can achieve this within a hierarchy by sticking to extending.  However, it is not as easy to do this across classes.  Think about a physical address when compared to an email address.  These are very different collections of properties.  When you consider a "print" method for these, there are multiple issues to consider. Is there an expected limit to the length? Do we allow multiple lines? Should we include field labels? Are there multiple formats (CSV, XML, JSON, etc.) to be supported? When designing for polymorphic behavior, the answers to these questions should be the same across all instances of the method.  For example, we should not allow some print functions to go multi-line while others keep all the values to one line (when a class has multiple attributes).  Likewise, we should not have a "save" that does not pair properly with its corresponding "load" method.  Development is difficult enough, do not add to our headaches with seemingly random functionality across methods with the same name.  

The AI-powered Podcast Player

Save insights by tapping your headphones, chat with episodes, discover the best highlights - and more!
App store bannerPlay store banner
Get the app