Skip to main content

Working with Legacy code - Sprout Method, Sprout Class and Wrap Method, Wrap Class

 Wrap Method

Adding behavior to existing methods is easy to do, but often it isn’t the right

thing to do. When you first create a method, it usually does just one thing for a

client. Any additional code that you add later is sort of suspicious.

Advantages and Disadvantages

Wrap Method is a good way of getting new, tested functionality into an application when we can’t easily write tests for the calling code. Sprout Method and

Sprout Class add code to existing methods and make them longer by at least

one line, but Wrap Method does not increase the size of existing methods.

Another advantage of Wrap Method is that it explicitly makes the new functionality independent of existing functionality. When you wrap, you are not

intertwining code for one purpose with code for another.


The primary disadvantage of Wrap Method is that it can lead to poor names. In the previous example, we renamed the pay method dispatchPay() just because we needed a different name for code in the original method. If our code isn’t terribly brittle or complex, or if we have a refactoring tool that does Extract Method (415) safely, we can do some further extractions and end up with better names. However, in many cases, we are wrapping because we don’t have any tests, the code is brittle and those tools aren’t available.

Wrap Class The class-level companion to Wrap Method is Wrap Class. Wrap Class uses pretty much the same concept
...
This technique is called the decorator pattern. We create objects of a class that wraps another class and pass them around. The class that wraps should have the same interface as the class it is wrapping so that clients don’t know that they are working with a wrapper. In the example, LoggingEmployee is a decorator for Employee. It needs to have a pay() method and any other methods on Employee that are used by the client.

The Decorator Pattern Decorator allows you to build up complex behaviors by composing objects at runtime. For example, in an industrial process-control system, we might have a class called ToolController with methods such as raise(), lower(), step(), on(), and off(). If we need to have additional things happen whenever we raise() or lower() (things such as audible alarms to tell people to get out of the way), we could put that functionality right in those methods in the ToolController class. Chances are, though, that wouldn’t be the end to the enhancements. Eventually, we might need to log the number of times we turn the controller on and off. We might also need to notify other controllers that are close by when we step so that they can avoid stepping at the same time. The list of things that we can do along with our five simple operations (raise, lower, step, on and off) is endless, and it won’t do to just create subclasses for each combination of things. The number of combinations of those behaviors could be endless. The decorator pattern is an ideal fit for this sort of problem. When you use decorator, you create an abstract class that defines the set of operations you need to support. Then you create a subclass that inherits from that abstract class, accepts an instance of the class in its constructor, and provides a body for each of those methods. Here is that class for the ToolController problem:

abstract class ToolControllerDecorator extends ToolController
    protected ToolController controller;
    public ToolControllerDecorator(ToolController controller) {              this.controller = controller; 
    } 
    public void raise() { controller.raise(); } 
    public void lower() { controller.lower(); } 
    public void step() { controller.step(); } 
    public void on() { controller.on(); } 
    public void off() { controller.off(); } 
}

Wrap Class This is a fine way of adding functionality when you have many existing callers for a method like pay(). However, there is another way of wrapping that is not so decorator-ish. Let’s look at a case where we need to log calls to pay() in only one place. Instead of wrapping in the functionality as a decorator, we can put it in another class that accepts an employee, does payment, and then logs information about it. Here is a little class that does this: class LoggingPayDispatcher { private Employee e; This class might not look very useful, but it is. You can subclass it and override any or all of the methods to add additional behavior. For example, if we need to notify other controllers when we step, we could have a StepNotifyingController that looks like this: 

public class StepNotifyingController extends ToolControllerDecorator { 
    private List notifyees;
    public StepNotifyingController(ToolController controller, 
       List notifyees) { 
           super(controller); this.notifyees = notifyees;
    }
    public void step() {
        // notify all notifyees here 
        ... 
        controller.step();
    }

The really neat thing is that we can nest the subclasses of 

ToolControllerDecorator: ToolController controller = new StepNotifyingController( new AlarmingController (new ACMEController()), notifyees); 

When we perform an operation such as step() on the controller, it notifies all notifyees, issues an alarm, and actually performs the stepping action. That latter part, actually performing the step action, happens in ACMEController, which is a concrete subclass of ToolController, not ToolControllerDecorator. It doesn’t pass the buck to anyone else; it just does each of the tool controller actions. When you are using the decorator pattern, you need to have at least one of these “basic” classes that you wrap around. Decorator is a nice pattern, but it is good to use it sparingly. Navigating through code that contains decorators that decorate other decorators is a lot like peeling away the layers of an onion. It is necessary work, but it does make your eyes water.
 ...

Summary In this chapter, I outlined a set of techniques you can use to make changes without getting existing classes under test. From a design point of view, it is hard to know what to think about them. In many cases, they allow us to put some distance between distinct new responsibilities and old ones. In other words, we start to move toward better design. But in other cases, we know that the only reason we’ve created a class is because we wanted to write new code with tests and we weren’t prepared to take the time to get the existing class under test. This is a very real situation. When people do this in projects, you start to see new classes and methods sprouting around the carcasses of the old big classes. But then an interesting thing happens. After a while, people get tired of sidestepping the old carcasses, and they start to get them under test. Part of this is familiarity. If you have to look at this big, untested class repeatedly to figure out where to sprout from it, you get to know it better. It gets less scary. The other part of it is sheer tiredness. You get tired of looking at the trash in your living room, and you want to take it out. Chapter 9, I Can’t Get This Class into a Test Harness, and Chapter 20, This Class Is Too Big and I Don’t Want It to Get Any Bigger, are good places to start.

Comments

Popular posts from this blog

AWS Elasticache Memcached connection

https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/accessing-elasticache.html#access-from-outside-aws http://hourlyapps.blogspot.com/2010/06/examples-of-memcached-commands.html Access memcached https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/GettingStarted.AuthorizeAccess.html Zip include hidden file https://stackoverflow.com/questions/12493206/zip-including-hidden-files phpmemcachedadmin ~ phpMyAdmin or phpPgAdmin ... telnet mycachecluster.eaogs8.0001.usw2.cache.amazonaws.com 11211 stats items stats cachedump 27 100 https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/VPCs.EC.html https://lzone.de/cheat-sheet/memcached VPC ID Security Group ID (sg-...) Cluster: The identifier for the cluster memcached1 Creation Time: The time (UTC) when the cluster was created January 9, 2019 at 11:47:16 AM UTC+7 Configuration Endpoint: The configuration endpoint of the cluster memcached1.ahgofe.cfg.usw1.cache.amazonaws.com:11211 St...

Notes Windows 10 Virtualbox config, PHP Storm Japanese, custom PHP, Apache build, Postgresql

 cmd => Ctrl + Shift + Enter mklink "C:\Users\HauNT\Videos\host3" "C:\Windows\System32\drivers\etc\hosts" https://www.quora.com/How-to-create-a-router-in-php https://serverfault.com/questions/225155/virtualbox-how-to-set-up-networking-so-both-host-and-guest-can-access-internet 1 NAT + 1 host only config https://unix.stackexchange.com/questions/115464/how-to-properly-set-up-2-network-interfaces-in-centos-running-in-virtualbox DEVICE=eth0 TYPE=Ethernet #BOOTPROTO=dhcp BOOTPROTO=none #IPADDR=10.9.11.246 #PREFIX=24 #GATEWAY=10.9.11.1 #IPV4_FAILURE_FATAL=yes #HWADDR=08:00:27:CC:AC:AC ONBOOT=yes NAME="System eth0" [root@localhost www]# cat /etc/sysconfig/network-scripts/ifcfg-eth1 # Advanced Micro Devices, Inc. [AMD] 79c970 [PCnet32 LANCE] DEVICE=eth1 IPADDR=192.168.56.28 <= no eff => auto like DHCP #GATEWAY=192.168.56.1 #BOOTPROTO=dhcp BOOTPROTO=static <= no eff ONBOOT=yes HWADDR=08:00:27:b4:20:10 [root@localhost www]# ...

Rocket.Chat DB schema

_raix_push_notifications avatars.chunks avatars.files instances meteor_accounts_loginServiceConfiguration meteor_oauth_pendingCredentials meteor_oauth_pendingRequestTokens migrations rocketchat__trash rocketchat_cron_history rocketchat_custom_emoji rocketchat_custom_sounds rocketchat_import rocketchat_integration_history rocketchat_integrations rocketchat_livechat_custom_field rocketchat_livechat_department rocketchat_livechat_department_agents rocketchat_livechat_external_message rocketchat_livechat_inquiry rocketchat_livechat_office_hour rocketchat_livechat_page_visited rocketchat_livechat_trigger rocketchat_message rocketchat_oauth_apps rocketchat_oembed_cache rocketchat_permissions rocketchat_raw_imports rocketchat_reports rocketchat_roles rocketchat_room rocketchat_settings rocketchat_smarsh_history rocketchat_statistics rocketchat_subscription rocketchat_uploads system.indexes users usersSessions https://rocket.chat/docs/developer-guides/sc...