3

I've seen code like the following before, which results in a sort of "extension access method" being added to an object. The extension method will appear in intellisense as only one method, but when selected, intellisense will appear with all the methods defined in the "manager" class. It seems like a nice way of organizing a set of similar functionality and decluttering the main intellisense of the primary object.

So, I'm wondering if this technique has some sort of commonly used name, and also whether it's considered a code smell (beyond the general problem of the primary object taking on too much responsibility and getting too large).

public class StringManager
{
    public StringManager(String value)
    {
        Value = value;
    }

    private String Value { get; set; }

    public int GetTwiceLength()
    {
         return Value.Length * 2;
    }

    public decimal GetHalfLength()
    {
         return Value.Length / 2;
    }
}

public static class StringExtensions
{
    public static StringManager Operations(this String value)
    {
        return new StringManager(value);
    }
}

the above code would be used like so:

var myString = "the string";
var twiceLength= myString.Operations().GetTwiceLength();

Apologies for the silly functionality. This was borrowed from an example on SO where the technique was actually recommended, modified to protect the potentially guilty.

jpt
  • 162
  • 1
  • 11
  • 3
    It's not a pattern at all. Since you can only do one operation at a time, you might as well hang the extension methods off the String class, and avoid the Manager class entirely. – Robert Harvey Mar 17 '20 at 19:44
  • In other words, it's a lot of additional complexity for no additional benefit. You might want to cite the original source, as I suspect you might have lost something in translation with your example. – Robert Harvey Mar 17 '20 at 19:45
  • @RobertHarvey - um, you can do two operations, and potentially hundreds if you want to add them to the StringManager class, all organized away behind the single "Operations()" method off the main object. I only gave the example of calling one of the two. – jpt Mar 17 '20 at 19:47
  • 1
    You can't chain them together, though. The only benefit here is Intellisense, and you'll still get that with String extensions. I'm saying the additional weight to carve off a portion of intellisense is probably not worth it. – Robert Harvey Mar 17 '20 at 19:48
  • @RobertHarvey, true, but all the extension methods would appear off the main object, which can create a very long selection set to wade through. This organizes the methods into topics of a sort. That's the point. – jpt Mar 17 '20 at 19:49
  • The closest pattern match is going to be a Builder Pattern. – Robert Harvey Mar 17 '20 at 19:49
  • @RobertHarvey, I am familiar with Design Patterns and this seems nothing like a Builder pattern. That said, I have nothing better to offer, either :-) – jpt Mar 17 '20 at 19:51
  • You can corral the Intellisense with well-designed interfaces in a Builder. – Robert Harvey Mar 17 '20 at 19:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209806/discussion-between-jpt-and-robert-harvey). – jpt Mar 17 '20 at 19:53
  • I personally do no like using extension methods... In my opinion they are a code smell and they are not object oriented. – Jonathan Alfaro Mar 17 '20 at 23:28
  • If you think extension methods are a code smell, then C# must stink to high heaven! It relies on them for some of its best functionality. – jpt Mar 18 '20 at 11:40

1 Answers1

1

This looks like the adapter pattern.

Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate. – [https://refactoring.guru/design-patterns/adapter]

Let me explain why I think that applies here:

Extension methods in C# are just regular static methods with just a little bit of syntactic sugar. The this keyword for the first parameter of the function allows you to call it as if it were an instance method on that type. But you could just as well call it like a regular static method:

StringExtensions.Operations("your string").CallMethtod();

Looking at the Operations implementation:

public static StringManager Operations(this String value)
{
    return new StringManager(value);
}

We can see that this is a plain old static factory method which returns an instance of your Manager class (= the adapter). This is equivalent to creating the apapter instance directly (but actually favorable, since it hides "how" new instances are created. You could for example pool and reuse existing instances):

new StringManager("your string").CallMethod();

Your Manager class adapts the string type/interface to be compatible with a different interface, providing all your "management methods" instead of the original string methods. To make the "adapter" explicitly visible, it helps to use an intermediate variable (for demonstration purposes):

var originalString = "your string"; var adaptedString = new StringManager(originalString); adaptedString.CallMethod();

Is it a code smell? I don't think so, not necessarily. You are only adapting the interface of one class to a different interface (string interface to StringManager interface). One might argue that "Manager" should not be used in class names, because it does not add any helpful context and could basically mean anything. Sooner or later all your classes will be some sort of Manager or Service or ManagerService.

As for all patterns: use them when they make sense and provide benefit. Do not overuse them: Do not use design patterns just for the sake of using a pattern. Sometimes it is even better to not use a pattern, even if one would exist or to introduce smells on purpose (use code comments to explain why something was implemented in that way). Use sensible judgement.

knittl
  • 246,190
  • 53
  • 318
  • 364
  • While I can see the similarities, the purpose behind the stated strategy doesn't really seem to fit the definition of an Adapter. Based on my perception of the different patterns: **Adapter** provides a different interface to the wrapped object, **Proxy** provides it with the same interface, and **Decorator** provides it with an enhanced interface. (I actually found this statement at Refactoring.Guru, but it fits with my understanding). So to me it seems that, of the 3, a Decorator would be the better choice. – jpt May 10 '20 at 14:39
  • @jpt well, your `StringManager` provides the clients with a different interface than the original `string` interface. You don't have `.Length` property anymore, but `GetTwiceLength()` method – different interface. AFAIU decorator exports the same interface, but performs additional actions. Consider a super-simplified list-like interface (or maybe Queue) with a single method `.Add(object)`. A normal implementation would simply add the object to the data structure. You could now create a `ListItemDuplicator` decorator which, well, decorates your `ListLike` interface [cont.] – knittl May 10 '20 at 14:56
  • [cont.] an implementation of the Duplicator decorator could look like: `class Duplicator : ListLike { readonly ListLike original; Duplicator(ListLike original) { this.original = original; } public Add(object obj) { original.Add(obj); original.Add(obj); }`. It still exports the same interface, but it "enhances" the functionality of the wrapped object with additional functionality (e.g. logging, validation, …), [adding responsibilities](https://stackoverflow.com/a/60478875/112968). The proxy pattern usually focuses on controlling access to an object. – knittl May 10 '20 at 15:05
  • I think for my purposes, you're focusing too much on the StringManager interface and not enough on the end effect on the string class's appearance to the programmer, which is where my interest lies. I appreciate the discussion though. Very informative, esp. in light of the Decorator pattern. – jpt May 11 '20 at 16:21
  • The appearance to the programmer is the class `StringManager`, not `string` anymore IMO. Whether or not you get the StringManager instance through an extenion method, a static factory or a plain old constructor call is irrelevant. – knittl May 11 '20 at 17:47
  • In my experience with using this pattern before, the (using) developer really only sees the functionality as hanging off the string object (via the IDE's intellisense), so it's relevant and sort of the point. The StringManager isn't mine, by the way, just an example I got from some blog somewhere. – jpt May 11 '20 at 17:53
  • The way I see it (personal opinion again), is that by calling `Operations()` you are effectively converting the string object into something else. When you call `new List().Select(s => s.ToUpper()).ToHashSet()`, would you say that `.ToHashSet()` is a functionality of `List` or of `Enumerable`? Or `"abc".ToCharArray().Length`, is `Length` a string functionality or a character array functionality? (not 100% comparable, because `ToCharArray()` accesses the innards of string, whereas your suggested `Operations()` does not). – knittl May 11 '20 at 17:59