3

I have several classes like Button, Textbox and so on but at instantiation of those object they all need one object reference. The button represents a physical button on the screen but it is not an UI element. Instead it should be used it Selenium to interact with the physical buttons.

 public class Button : Element, IButton
 {
 public Button(IService someService) : base()
 {
 }
 }

somewhere later

var b = new Button(someService)
var b = new Button(someService)
var b = new Button(someService)
var b = new Button(someService)

The problem now is that thoughout the application I need to instantiate this button object several times so I always need to pass the reference inside the constructor. This to mee looks like not so good code. So if anybody has a clue on how to do this better I would be very grateful!

Thanks in advance

asked Apr 16, 2020 at 13:26
6
  • You essentially need something like a configurable constructor - in other words, a factory that you can pass that dependency to, so that you can get a preconfigured button object on demand. Alternatively, if it's easier to make a copy of an existing button, you could use something like a the Prototype pattern where you create the "prototypal" instance once with the dependencies, and then copy that instance on demand. The third option is what's suggested in Yerg's answer below - it's the Service Locator (anti)pattern; this obscures the dependency structure, though, and might interfere with TDD Commented Apr 16, 2020 at 14:35
  • 1
    Another thing to consider is if a button is at the right level of granularity for this kind of dependency - what you're doing is not necessarily wrong, but maybe you could restructure so that this dependency is passed to the containing view, rather than to the button. Commented Apr 16, 2020 at 14:35
  • How is this service being used? Does it perform logic when the button is clicked, rendered? Generally speaking, what kind of logic is it? Commented Apr 16, 2020 at 15:01
  • Yeah the name is a bit misleading as it is not a UI element of any sort more a conceptual data representation. So the context is test automation, so the button will represent a physical button on the screen. Commented Apr 16, 2020 at 15:02
  • So the Button class is used by your tests as a means to interact with the real buttons in an application. Ok. That is critical information. Please edit your question and include information about what this Button class is and how it is used. Even providing a code example of its usage would be great. Plus, we need info about what this "service" class does. What is currently creating these Button objects? What automation framework are you using? Commented Apr 16, 2020 at 15:08

3 Answers 3

1

I would recommend to start with a very simple improvement: just refactor the button instantiation into a method which you can reuse throughout your code.

 Button CreateButton()
 {
 return new Button(someService);
 }

Of course, one to decide where to put this method, which depends on where it is required. In case it is required inside only one class, one can put the method simply there.

In case the method is required in several different classes in your application, the method could be placed into a factory class, where someService is a member variable of that factory, which is passed through the constructor of the factory:

 class ButtonFactory
 {
 ButtonFactory(someService)
 {
 this.someService = someService;
 }
 Button CreateButton()
 {
 return new Button(someService);
 }
 }

Now you have to initialize the factory once in your application with the correct service, and reuse it throughout the application where it is required.

The code which uses this will then look like

 Button button = buttonFactory.CreateButton();

which is not shorter than the original. But the benefit is that you can now pick the correct service once, in one place of the application, and create the buttons elsewhere. In case you want to pick a different service later, there will be only one place (the factory construction) to be changed in the code.

If text boxes require the same service, you can extend the factory class by adding a similar method CreateTextbox() to it (of course, the factory then should be renamed to something like ElementFactory or ControlFactory).

answered Apr 16, 2020 at 14:44
1

As an additional idea for improvements, one may consider not to make the general Button class directly dependent from something like a special service.

The button represents an UI element. Maybe one wants to reuse it in different contexts. Tying it to something very application-specific like a business logic service prevents re-use across applications.

Observer Pattern

Let the button do it's stuff and use polymorphism to decouple that from what should happen when the button is interacted with:

(i'll use java syntax here, but you get the point):

interface Runnable {
 void run();
}
class Button {
 private List<Runnable> clickListeners = new List();
 public void addClickListener(Runnable listener) {
 this.clickListeners.add(listener);
 }
 private void onMouseLeftClicked() {
 if (this.disabled) return;
 this.clickListeners.forEach(listener -> listener.run());
 }
}
class MyApplicationWindow extends Window {
 public MyApplicationWindow(Runnable onFormSubmit) {
 // construct all of the UI layout and stuff
 Button submitButton = new Button();
 // here is where you connect UI and business logic:
 submitButton.addClickListener(onFormSubmit);
 }
}
// in your application bootstrap:
Service myService = ... ;
Runnable submitHandler = () -> myService.doStuff(getDataFromUI());
// when constructing your UI, pass a reference to the submit handler.
// note: conceptually, it is entirely up to the UI to decide when to trigger the
// submit; thats perfect because interpreting user intent is part of the UIs job
// whereas performing the action is part of your service layer
Window myApplicationWindow = new MyAplicationWindow(submitHandler)

As to the repetition: for properties/configuration common to all buttons you can use the factory pattern, similar to what Doc Brown wrote in his answer.

Doc Brown
219k35 gold badges405 silver badges619 bronze badges
answered Apr 16, 2020 at 14:54
10
  • You make a lot of assumptions of what this service is, though the OP did not provide any information about it. It may be perfectly possible that the "service" is nothing but a similar kind of listener which is scetched in your answer. Commented Apr 16, 2020 at 16:25
  • @DocBrown Yes, thats very true. If the service only affects maybe the looks or text of the button, i'd totally agree to your factory pattern solution. However, this is a button. And basically any UI framework i've ever seen does button interactions with observers and the vast majority of programmers have no problem with this approach. So i thought it's much more likely that observer is the right solution. Commented Apr 17, 2020 at 6:41
  • 1
    "However, this is a button" - according to the OP's last edit, it is probably not exactly the kind of button you envisioned here, Commented Apr 17, 2020 at 9:12
  • You are right, that invalidates my answer. @DocBrown If you make an edit to your answer i can change my vote to +1 on it :) Commented Apr 17, 2020 at 10:28
  • IMHO there is no need to see this so "black and white": maybe the OPs design with a service injected into a button is fine, maybe it is not - we simply don't know based on the few pieces of information we got. So when writing an answer, to my experience it is a good idea to tell which assumptions were made and write something like "under these assumptions, consider the following approach..." instead of insisting in something which sounds like "that button must never ever have a reference to a service, that is verboten".... Commented Apr 17, 2020 at 20:34
-2

I think this problem is in the domain of something called "Inversion of control".

You can either use an already implemented IOC(Inversion of control) framework or make something very lightweight for this, basically you would have:

class someService : ISomeService {}
public static class DependencyLibrary
{
 public static T Instantiate<T>()
 {
 if(T is ISomeService)
 return new SomeService();
 return null;
 }
}
public class Button : Element, IButton
{
 private ISomeService _someService
 public Button() : base()
 {
 _someService = DependencyLibrary.Instantiate<ISomeService>();
 }
}

DependencyLibrary is a class whose whole function is to keep references to resources and serve them to anybody who asks for a class of that type. Instead of returning a new SomeService every time it can cache the reference inside a list and give that back instead.

This gives you more separation between layers, since now neither the Button, neither the using class needs to know what ISomeService actually is, they can work with whatever the library gives back. If this service is a database for example, you can switch between the testing database and the production one with ease.

If you are interested you can take a look at these resources:

In-depth look at injection

IOC frameworks list for C#

answered Apr 16, 2020 at 14:26
4
  • With other words I should use something like autofac, right? Commented Apr 16, 2020 at 14:33
  • Yes, I think it covers this case perfectly. Bringing in a big project under an ioc just for this issue would be a pretty big undertaking, but cleaning up the dependencies imo is really beneficial. Commented Apr 16, 2020 at 14:43
  • 2
    Isn't this the service locator anti-pattern? Commented Apr 16, 2020 at 14:43
  • I think that depends on what SomeService is. Since it was the same reference that was given to all buttons I made the assumption that its something you only need to reference once, and the button's constructor is used as a mechanism to create something similar to a static variable Commented Apr 16, 2020 at 14:48

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.