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
3 Answers 3
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
).
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.
-
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.Doc Brown– Doc Brown2020年04月16日 16:25:13 +00:00Commented 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.marstato– marstato2020年04月17日 06:41:11 +00:00Commented 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,Doc Brown– Doc Brown2020年04月17日 09:12:23 +00:00Commented 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 :)marstato– marstato2020年04月17日 10:28:34 +00:00Commented 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"....Doc Brown– Doc Brown2020年04月17日 20:34:52 +00:00Commented Apr 17, 2020 at 20:34
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:
-
With other words I should use something like autofac, right?John– John2020年04月16日 14:33:27 +00:00Commented 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.Yerg– Yerg2020年04月16日 14:43:20 +00:00Commented Apr 16, 2020 at 14:43
-
2Isn't this the service locator anti-pattern?Rik D– Rik D2020年04月16日 14:43:58 +00:00Commented 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 variableYerg– Yerg2020年04月16日 14:48:39 +00:00Commented Apr 16, 2020 at 14:48
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?