Develpreneur: Become a Better Developer and Entrepreneur

Rob Broadhead
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.  
undefined
Feb 26, 2021 • 15min

Polymorphism Without Side Effects - Object-Oriented Clarity

We discussed in the previous episode how polymorphic behavior gives us a form of a common language for objects.  Thus we need to consider the idea of polymorphism without side effects, so we have clear and concise commands.  There is also a consistency required for this to be an approach that is truly useful. What Is Polymorphism Without Side Effects As always, we should start with a definition of terms.  In this case, our goal is clarity.  Polymorphism is a way to give a command each object responds to.  That means there should be similar results for each one.  As an example, if I tell several people to "get the mail," I should be able to assume they either check a physical mailbox or maybe an electronic one.  I should not have some people make me lunch or pay the bills as part of that command. The Danger of Intent This challenge revolves around intent.  In our mail example, there are logical assumptions that can be made.  These include scope and other restrictions.  When I ask someone to get the mail, it implies a one-time task and not something that will be done forever.  Likewise, it does not generally imply sending mail at the same time (or paying bills).  These unintended consequences can be described as side-effects.  They can be confusing and even damaging. The Power of Clarity We will look at several good habits that make object-oriented programming work well.  Clarity and consistency are two of these.  When we use the same command for different work, it becomes confusing and impacts the user experience.  Instead, we should aim for polymorphism without side effects by clearly defining actions and publicly visible properties.  We can do this by adding context (e.g., printToScreen, printToFile) or other descriptive terms (e.g. printAsXML, printAsJSON)  
undefined
Feb 24, 2021 • 15min

Polymorphism Overview - Reducing code size and a better user experience

We start the next series of episodes with a polymorphism overview.  This is a core concept for proper object-oriented design.  Likewise, we will dig into several practical ways to use this. Polymorphism Overview - A Definition As with many topics, it seems best to start with a definition from Wikipedia. In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. For our purposes, that symbol that is referred to can be considered a name.  The name can be a method, class, or property name. Class Vs. Object It is worth clarifying the difference between a class and an object.  We will be talking about these two terms a lot from here on out.  Therefore, let's remove any confusion.  A Class is the definition of a class.  Think of it as a set of rules or a template.  A simple pseudo-code example is below. Class MyExample { String name; String value; public whoAmI() { return this.name; } } An object or instance is an occurrence of a class.  In the example below, myPointer is an object (or instance) of the class MyExample.  These are simple examples.  However, the concept is simple.  In the real world, an example would be that there is a class called Mother, and your mom is an instance of that class. myPointer = new MyExample(); The General Concept And Examples Since this is a polymorphism overview, we now need to talk about examples.  In general, polymorphism allows us to provide commands to objects they can follow for their specific case.  We have examples of this throughout the real world via commands we give and questions we ask.  We can look at the request "tell me about yourself" as an example.  The response may be a name, a profession, a season of life, an entire life story, or countless other responses.  In this case, the object the person you are talking to will take that request and polymorphically respond.  It is polymorphic because the same command is understandable by each of us.  However, we will have different responses due to our personality (or class). In the code world, a command like this may be "save" or "print" or myriad other commands.  These allow us to "tell" a group of objects the same command and have each respond in a pertinent way. 
undefined
Feb 22, 2021 • 14min

Data Hiding - Practical Accessors

We have discussed the accessor levels of our data.  In most environments, the three levels are public, protected, and private.  In this episode, we go deeper into these concepts and a practical approach to data hiding. The Private Access Level This level of access should be our default.  It can be considered the critical step in data hiding.  Private hides our data and methods.  I find a physical example often works best for the goal of this approach.  When we purchase a physical device, we prefer fewer buttons and interface widgets over more.  The Apple iPod was a perfect example of this.  There were very few controls, so the interface was easy to understand.  Our software should follow the same principle of less is more.  When we do so, we keep things simple and avoid paralysis from providing too many options. Protected Access A well-designed object-oriented system will have helpers and relationships among objects.  Therefore, it requires an object to be able to modify values or have access to internal methods directly.  These are often "administrative" actions that we do not need to provide to the general public.  However, they are needed for classes to embrace all that their definition requires.  This situation most often shows up in inherited classes.  An object that is a subtype will still need to access core items.  This does not require a change to attributes.  We can keep those private while providing protected access via methods.  That is often the better approach as it allows us to make sweeping changes to a system via core classes without rewriting the children and helpers. No Data Hiding - Public Access The most open level of access is public.  This should be rare in most systems and well-defined.  A user should be able to understand the direct and ancillary impact of calling a public method.  Likewise, it should rarely have anything other than a direct impact on an object.  The simple interface is always the best approach.  
undefined
Feb 19, 2021 • 15min

A Practical Approach to Data Encapsulation

We have developed many bad habits over the years as OOP has proliferated frameworks and tools.  In this episode, we look at the practical side of data encapsulation and access levels like private, protected, and public. Tools Are A Beginning Modern frameworks and tools provide ways to generate a general object-oriented solution quickly.  Therefore, the tools will give us a start, but not the best solution.  There are too many details the tools are not privy to that are essential to the best solution.  We will make the best use of tools when we remember this fact.  That means we can not only move forward with generated code.  We must review what is provided and extend or remove details to fit our needs. A perfect example of tools being a less-than-perfect fit is in data hiding.  There are general assumptions made for accessors like getters and setters that should be refined.  A solution does not know that you have a read-only property or one that should not be directly passed through on a request. Data Encapsulation in a Nutshell The goal of data encapsulation is for us to provide a form of just-in-time access to data within our system.  This approach can also be viewed as making attributes and methods available on a need-to-know (or need-to-use) basis.  Once we expose a method or value, we can not undo that.  There are plenty of examples in frameworks where a feature or value is deprecated.  That occurs when something was exposed that now should not be.  Thus, the author is warning users that the deprecated element will be disappearing in the future.  An ideal approach would be not to expose that feature in the first place.  
undefined
Feb 17, 2021 • 13min

Data Hiding - A Need To Know Software Approach

Data encapsulation and data hiding are terms for keeping object properties from public consumption.  This concept is an essential part of object-oriented programming.  We need to be able to have internal processes and values that we can change without impacting users.  It also allows us to limit the impact of changes in large systems.  Thus, we will start our OOP season with a look at this somewhat simple concept. Data Hiding is more than properties. One important facet of this topic is that we use encapsulation for more than attributes or properties.  That may not be obvious in the frameworks you use.  Therefore, we need to look at these expanded options for encapsulation.  We also can use this as an opportunity for expanding on object-oriented design. The core point I want to make is that a method and a property are rough equivalents in the OOP world.  We (the consumer) make a request, possibly include parameters, and then expect a result.  When we deal with a property, we say, "give me that property."  A method is roughly "give me that calculated value."  Consider the case where there is no calculation required.  Thus, we have the idea of "getters" and "setters."  These are often simple passthrough functions. Expanding Complexity Now we have stumbled on why data encapsulation is useful.  Consider an attribute, a date, for example.  There are numerous Date formats and permutations.  We can include a time (or not), consider day or week (or not), and other options.  Thus, a method of getDate() can quickly evolve from returning a string of "Monday" to "2/3/2019" to "2/3/2019 08:33".  While the caller may want to handle those results differently, they do not want to change their code every time you change the underlying data type.  That means you may start with getDate() returning "Monday" and then can later store it as a date and add a getFullDate() method that returns a YYYY-MM-DD format for that same variable. Data Hiding in Practice The best way to think about data encapsulation is using a "need to know" approach.  There is no implicit benefit in exposing an implementation detail like a property type or helper method.  Therefore, please do not provide a public interface unless it is needed for consumers.  Otherwise, you are effectively providing users enough rope to hang themselves.  That will lead to them being unhappy when you are forced to change your internal design.

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