Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Utility Classes

Utility Classes

(download for better quality) - topics on Utility Classes

tags: class cohesion, class methods, cohesion, coincidental cohesion, encumbrance, java, logical cohesion, math, mixed-domain cohesion, object, scala, singleton, static methods, utility

Philip Schwarz

April 28, 2019
Tweet

More Decks by Philip Schwarz

Other Decks in Programming

Transcript

  1. Utility Classes @philip_schwarz slides by topics on this type of

    class, based on extracts from the following
  2. Cohesion Cohesion has to do with how focused a software

    module is. A module is a clump of software such as a block of code, a method, a class or even a package. Focus, in this instance, means how well does each subcomponent of the module relate to the overall purpose of the module. High cohesion indicates that all pieces of the module contribute to the overall purpose, while low cohesion indicates that few of the subcomponents of the module contribute to the module's purpose. High cohesion is desirable. Larry Constantine first identified cohesion as a software design principle in 1975 as part of his structured design process. Constantine enumerated seven variations of cohesion ordered from low (less desirable) cohesion to high (more desirable) cohesion. These variants constitute a continuum and include: • Coincidental cohesion • Logical cohesion • Temporal cohesion • Procedural cohesion • Communicational cohesion • Sequential cohesion • Functional cohesion ... It can be tricky to figure out what type of cohesion a software module has… ... Classifying module cohesion can be confusing as the differences between the modules can be very subtle. It is probably less useful to worry about the exact classification of the module and more important to get the general vicinity of the classification correct... https://www.linkedin.com/in/steve-halladay-63b1441/
  3. Coincidental Cohesion Coincidental cohesion occurs when the subcomponents of a

    module have virtually nothing in common. As an illustration of coincidental cohesion, imagine a junk drawer in a kitchen. When you open the junk drawer, you might find a screwdriver, miscellaneous keys, a stick of chewing gum, a roll of packing tape, some sun block and a coupon for 50% off on a car wash. What do these items have in common? They have nothing in common except they all reside in the junk drawer. The only reason these items are in the drawer is because the owner didn't know what else to do with them. So it is with modules of coincidental cohesion. Examples of coincidental cohesion in code might include a "Main" class that contains the main method of a program as well as all other static methods the program may use. Inexperienced programmers often create these classes more by accident than on purpose. Programmers want to write a program and give little thought to the organization, so the coincidental cohesive module just sot of emerges from the lack of organized thinking. Module 3 uses coincidental cohesion. Notice that none of the methods really have much to do with each other except that they are utilities that don't seem to belong anywhere else. The complete lack of relevance between the utilities means that we should classify the module as coincidental cohesion. // Module 3 class Utilities { int hashString(String str) { ... } int generateRandomInteger() { ... } void displayLogo() { ... } } Steve Halladay
  4. Logical Cohesion Modules with logical cohesion are those modules that

    have subcomponents that the developer grouped together because the subcomponents have the same categorization. For example, imagine a pantry with many shelves. Each shelf has a logical category of foods. On the top shelf are the drinks including soda pop, bottled water, energy drinks, fruit drinks and health drinks. On the next shelf are the ethnic foods. These include Mexican beans and peppers, Chinese sauces and Italian pasta and spices. The third shelf contains canned vegetables such as peas, carrots, olives and potatoes. Each of these three shelves is logically cohesive; the shelf contains items of the same category. So what is the problem with this organization? While there is nothing wrong with the organization, the shelf has little cohesion. For example, you usually will not use the Mexican foods in combinaton with the Chinese and Italian foods in a single meal, but you may use drinks from the top shelf, som ethnic foods from the second shelf and various vegetables from the third shelf. You will need to access all three shelves for just about any meal. While logical cohesion may help one find an item or subcomponent, the items or subcomponents do not work together for a single purpose. We see logical cohesion in utility classes like IO classes or math libraries. Consider a math library. It may have a method to calculate square roots, methods for trigonometric functions and maybe radix conversion. Few programs will use all of these methods for a single functional reason. So the math library has a weak, merely logical cohesion. // Module 6 class StringUtilities { String getReverse(String string) { ... } String getCamelCase(String string) { ... } String getRandomOrder(String string) { ... } } Module 6 uses logical cohesion. You might almost guess that module 6 is coincidental cohesion, but the difference is that the methods of this module have something in common - they all operate on strings. However, users of this module will seldom use more than one method at a time. The methods really don't work together. Therefore, Module 6 uses logical cohesion. Steve Halladay
  5. Class Methods in Java and Their Uses Class methods can

    be thought of as methods that are not a form of message passing to objects of that class and instead can be invoked independently of any objects of the class. In general, class methods are useful when objects of that class are stateless (i.e. have no instance variables) or when some of the methods do not use the state of the objects and instead merely manipulate the data passed in as parameters. For example, all the methods in the Math class are public class methods (they have the “public” and ”static” modifier in their declarations) and so can be accessed and executed by any body of Java code without reference to Math objects. For example, the sin method in the Math class can be executed as in double y = Math.sin(x); It is appropriate that these methods are class methods because they perform mathematical operations on the arguments passed to those methods, and so a Math object would play no significant role. When designing a class and figuring out what methods it should have, it is not always immediately obvious whether a method should be a class method or instance method. For example, suppose you were defining a Set class (different from the util.Set interface), objects of which behave like (finite, unordered) mathematical sets of integers. A natural operation to be performed on such a set is the intersection with another Set. There are (at least) two ways such an operation can be declared in your Set class: • public Set intersect(Set otherSet) • public static Set intersect(Set firstSet, Set secondSet) In the first case, the user would get the intersection by sending a Set s1 a message asking it to return the intersection of itself and a second set s2, through a method call such as: Set intersection = s1.intersect(s2); http://www.cs.colby.edu/djskrien/
  6. In the second case the user could find the intersection

    by calling the class method passing both sets as parameters: Set intersection = Set.intersect(s1, s2); Which version is better? One advantage of the second version is that it displays the natural symmetry in the intersection operation, in which neither set plays a special role. Also, the second version will not necessarily fail with NullPointerException if s1 or s2 happened to be null. That is, the intersect(Set, Set) class method in the second version could test the nullity of s1 and s2 and treat a null s1 or s2 as an empty set. In contrast, the call in the first version will throw an exception before the intersect(Set) instance method even begins execution if s1 is null. However, an advantage of the first version is that it is a natural way to proceed from an OO perspective. That is, it is natural to think of asking a Set object to tell you what it has in common with another set. Also, unless the intersect(Set) instance method is declared ”final”, it can be overridden by subclasses of Set, which although not obviously useful here, is a feature that future users may find very valuable… http://www.cs.colby.edu/djskrien/
  7. G18: Inappropriate Static Math.max(double a, double b) is a good

    static method. It does not operate on a single instance; indeed, it would be silly to have to say new Math().max(a,b) or even a.max(b). All the data that max uses comes from its two arguments, and not from any “owning” object. More to the point, there is almost no chance that we’d want Math.max to be polymorphic. Sometimes, however, we write static functions that should not be static. For example, consider: HourlyPayCalculator.calculatePay(employee, overtimeRate). Again, this seems like a reasonable static function. It doesn’t operate on any particular object and gets all it’s data from it’s arguments. However, there is a reasonable chance that we’ll want this function to be polymorphic. We may wish to implement several different algorithms for calculating hourly pay, for example, OvertimeHourlyPayCalculator and StraightTimeHourlyPayCalculator. So in this case the function should not be static. It should be a nonstatic member function of Employee. In general you should prefer nonstatic methods to static methods. When in doubt, make the function nonstatic. If you really want a function to be static, make sure that there is no chance that you’ll want it to behave polymorphically. Robert Martin (aka Uncle Bob) @unclebobmartin
  8. Static methods are like cancer in object-oriented software – once

    you let them get a toe-hold, it’s very difficult to get rid of them and their presence will only grow. Just stay away from them in the first place. “But I’ve got them everywhere!” – you may say – ”What to do?”. Well, what can I say… you are in trouble, like all of us. We have tons of open source libraries almost entirely made of utility classes (we’ll discuss them in the next section) and static methods. Like with a tumor, the best cure is a knife. Just don’t use that software, if you can afford not to. However, in most cases you won’t be able to afford a knife, since the libraries are very popular and provide really useful functionality. In that case, your best option is to isolate that tumor by creating your own classes that wrap static methods in order to let your code deal with objects. For example, there is a static method FileUtils.readLines() in Apache Commons, which reads all lines from a text file. Here is how we can turn it into an object: public class FileLines implements Iterable<String> { private final File file; public Iterator<String> iterator() { return Arrays.asList( FileUtils.readLines(this.file) ).iterator(); } } Now, in oder to read all lines from a text file, our software will do this: The static method call will happen only inside the class FileLines and eventually we’ll be able to get rid of it. Or maybe this will never happen. But the point is that we won’t have static method calls anywhere in our code. Well, just in one place, inside class FileLines. That’s how we isolate the deceased and deal with it incrementally. Iterable<String> lines = new FileLines(f); Yegor Bugayenko @yegor256
  9. 3.2.3 Utility Classes A so-called “utility” class is not really

    a class but a collection of static methods used by other methods for convenience (they are also known as “helpers”). For example, class java.lang.Math is a classic example of a utility class. These “creatures” are very popular in Java, Ruby, and almost every modern language, unfortunately. Why aren’t they classes? Because they don’t instantiate objects. In section 1.1 we discussed the difference between an object and a class, and agreed that a class is a “factory of objects”. A utility class is not a factory of anything. Here is an example: class Math { private Math() { // intentionally empty } public static int max(int a, int b) { if (a < b) { return b; } return a; } } Utility classes are a triumph of procedural programmers in the OOP domain. A utility class is not just a bad thing, as a static method is, but an aggregation of bad things. Every bad word I’ve said above about static methods can be said here again, but with multiplied emphasis. Utility classes are a terrible anti-pattern in OOP. Stay away from them. It is a good practice, for those who use utility classes, to create a private constructor like in this example to avoid instantiation of the “class”. Because the constructor is private, nobody can make an instance of the class except its own methods. Yegor Bugayenko @yegor256
  10. 3.2.5 Functional Programming I hear this argument rather often: they

    say that if your objects are small and immutable, and you don’t have static methods, why don’t you just use functional programming (FP)? Indeed, there is a lot of similarity between functions and objects, if objects are as “elegant” as this book recommends. So why do we need objects? Why not just use Lisp, Clojure, or Haskell instead of Java and C++? This is the class that represents a maximum of two integers, from section 3.2.1: class Max implements Number { private final int a; private final int b; public Max(int left, int right) { this.a = left; this.b = right; } @Override public int intValue() { return this.a > this.b ? this.a : this.b; } } This is how we’re supposed to use it: Number x = new Max(5, 9); This is how we would design a function in Lisp that would do exactly the same: (defn max (a b) (if (> a b) a b)) So why use objects? The Lisp code is much shorter. OOP is more expressive and powerful, because it has objects and methods, while FP only has functions. Some FP languages have objects too, but I consider them OOP languages with FP elements, not the other way around. I also think that Lambda Expressions in Java, as a move towards FP, make Java less solid, because they distract us from true object-oriented style. FP is a great paradigm, but OOP is better. Especially if done right. In an ideal OOP language, I think we would have classes and functions inside them. Not Java methods as micro-procedures, which we have now, but true functions in a pure FP paradigm with a single exit point. That would be the ideal situation. Yegor Bugayenko @yegor256
  11. 3.8 The Utility The utility (or utility package) is a

    group of procedures and functions encapsulated into a single unit with a set of private data. It differs from the class in that individual objects are never instantiated from it; the utility is more like a group of traditional functions and procedures (like a dynamically linked library) or an Ada-83 package. The utility is like a class with no objects. Its operations are, in effect, class operations. (Alternatively, you could regard a utility as a class with only one, predefined object). Figure 3.12 shows the UML notation for a utility. The class name is prefaced by the stereotype <<utility>>, enclosed in what my publishing friends tell me are guillemets…Notice that none of the operation names in Fig 3.1.2 is underlined. Although a utility’s operations are in effect class operations, by convention UML doesn’t underline their names. The utility is valuable for implementing a software construct with only one instance, such as a mathematics package or a daemon (A daemon is …). The utility is also useful in that it an provide a veneer of object orientation in programs written in traditional languages such as C or COBOL. A well-defined utility built around some venerable COBOL, or other legacy code is commonly called a wrapper. Meilir Page-Jones Exercise 6 Answer: see next three slides
  12. 9.3 Class Cohesion: A Class and Its Features Class cohesion

    is the measure of interrelatedness of the features (the attributes and operations) located in the external interface of a class. … Perhaps however, type cohesion would be an even better term then class cohesion, since with this concept we are trying to assess how well a class “hangs together as an implementation of some abstract data type”. A class with low (bad) cohesion has a set of features that don’t belong together. A class with high (good) cohesion has a set of features that all contribute to the type abstraction implemented by the class. … During my recent meanderings through object-oriented shops, I’ve observed three tell- tale cohesion problems in the allocation of features to classes: three problems that are observable from a class’s external design. I call these the three problems mixed-instance, mixed-domain and mixed-role cohesion. Of the three, mixed-instance cohesion is typically the greatest sin and mixed-role cohesion the least. A class can have all, some or none of these cohesion problems. A class with none of these three mixed cohesions is entirely cohesive and is said to have ideal cohesion… Meilir Page-Jones
  13. 9.3.2 Mixed-domain cohesion A class with mixed-domain cohesion contains an

    element that directly encumbers the class with an extrinsic class of a different domain. …but now I need to define “extrinsic.” The class B is extrinsic to A if A can be fully defined with no notion of B. B is intrinsic to A if B captures some characteristic inherent to A. For example, Elephant is extrinsic to Person, because in no sense does “elephant” capture some characteristic of a person. However Date (as in Date of birth) is intrinsic to Person. There are many examples of mixed-domain cohesion, some of which are obvous, some subtle. The first example that I saw was subtle: it was the Real (real number) class in a vendor’s class library, which had an attribute of arctan. I stared for a long time at this attribute, for I perceived something gravely wrong with its allocation to the class Real. However, although I realized the gravity of the problem, I couldn’t figure out exactly why I thought that arctan didn’t belong in Real. One day, …, an insight suddenly hit me… Real has no business messing around with objects of class Angle. Where would this designer stop with this encumbrance? How about adding an operation called convertTemp to the class Real so we could convert Fahrenheit to Celsius and Kelvin to Reaumur? That would encumber Real with Temperature. Or if we wanted to get euros for our dollars, we could encumber Real with Money. Or Real could even return the set of customers whose bank balances were (to the nearest cent) equal to some real number. The list of absurd possibilities is endless… Meilir Page-Jones Encumbrance measures the total ancillary machinery of a class. “Total ancillary machinery” comprises all the other classes that the given class must rely on in order to work. In other words, if you count all the classes referred to by a class C, and then count the classes that they refer to, and so on, the total number will be the encumbrance of C.
  14. When you design a class of a given domain, you’ll

    have to include classes from lower domains in your design – that’s what responsibility’s about. But make sure that you need those classes because of intrinsic properties of your class. For example, it would be fine for the class Account to have an operation that returned an object of class Money or even an attribute of class Date. However I’d be very suspicious if the class returned an object of the architecture-domain class WireXferLink. The architecture domain often gets mixed into a business-domain class. This is wrong – unless your business actually is buiding architectural infrastructure. The old object-oriented motto of “a thing should know how to something itself”, as in “a document should know how to print itself”, can be a little too appealing. A Document class with specific knowledge of a printer has mixed-domain cohesion. When you design a class of a given domain, you should be particularly weary of introducing classes of higher domains into the class you are designing. That’s why the class Real (above) was a problem. Real is a fundamental class and it shouldn’t be encumbered with classes of higher domains. Yet, in the poor design that I saw, Real’s attribute arctan (of class Angle) forced Real to deal with Angle, a class from a higher group of classes (namely the semantic group). Another way to get a sens of the relative domains of two classes is to ask the question, Can I imagine this class being built without this other class? I can imagine Real being built without the class Angle ever existing. However, I cannot envision building the class Angle without the class Real. This implies that Angle is in a higher domain than Real. Meilir Page-Jones
  15. Letting Objects Speak for Themselves Discovering and using this duck

    type improves the code by removing the Schedule’s dependency on specific class names, which makes the application more flexible and easier to maintain. However, Figure 7.2 still contains unnecessary dependencies that should be removed. It’s easiest to illustrate these dependencies with an extreme example. Imagine a StringUtils class that implements utility methods for managing strings. You can ask StringUtils if a string is empty by sending StringUtils.empty?(some_string). If you have written much object-oriented code you will find this idea ridiculous. Using a separate class to manage strings is patently redundant; strings are objects, they have their own behavior, they manage themselves. Requiring that other objects know about a third party, StringUtils, to get behavior from a string complicates the code by adding an unnecessary dependency. This specific example illustrates the general idea that objects should manage themselves; they should contain their own behavior. If your interest is in object B, you should not be forced to know about object A if your only use of it is to find things out about B. The sequence diagram in Figure 7.2 violates this rule. The instigator is trying to ascertain if the target object is schedulable. Unfortunately, it doesn’t ask this question of target itself, it instead asks a third party, Schedule. Asking Schedule if a target is schedulable is just like asking StringUtils if a string is empty. It forces the instigator to know about and thus depend upon the Schedule, even though its only real interest is in the target. Just as strings respond to empty? and can speak for themselves, targets should respond to schedulable?. The schedulable? method should be added to the interface of the Schedulable role. Sandi Metz @sandimetz