Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.java.programmer > #33391
| From | Marcel Mueller <news.5.maazl@spamgourmet.org> |
|---|---|
| Newsgroups | comp.lang.java.programmer |
| Subject | Re: Refactoring three very similar methods into one |
| Date | 2015-12-25 03:59 +0100 |
| Organization | MB-NET.NET for Open-News-Network e.V. |
| Message-ID | <n5ibei$9r1$1@gwaiyur.mb-net.net> (permalink) |
| References | <Refactoring-20151224220930@ram.dialup.fu-berlin.de> |
On 24.12.15 22.18, Stefan Ram wrote:
> I have three methods that look very similar to each other.
>
> I wonder whether I can refactor them into a single method,
> or at least the first two with the same type of parameter lists.
You can do so, but the Java language does not provide the required
compile time polymorphism to do this without runtime overhead.
> A reasonable condition, however, should be that the size of
> the total source code of the result of the refactoring does
> not exceed the original size.
Here you will fail. In theory the code of the unified result could be
smaller because of the reduced redundancy (including the function
definitions). But in practice you will need a FunctionalInterface and
lambda expressions or something similar to complete your task. This
overhead is probably overcompensating the reduced redundancy.
Furthermore because of the required virtual function call at runtime the
JIT can no longer inline the method call. This will impact execution
time. Whether this is important or not is another question. But function
inlining can be a significant speed up when the code is called often.
(Note that inlining in Java only works if the function or class is final
or static.)
> void setWidth( final android.view.View view, final int width )
> { final android.widget.LinearLayout.LayoutParams params
> = getParams( view );
> params.width = 0; view.setLayoutParams( params ); }
The main problem is that for the generic access to the field 'width' you
need a reference to this field within an arbitrary instance of
LayoutParams. Java does not have a type for this kind of references at
all. So you only can use a code fragment (lambda) to do the desired task
and call this fragment from your unified code.
> void setHeight( final android.view.View view, final int height )
> { final android.widget.LinearLayout.LayoutParams params
> = getParams( view );
> params.height = 0; view.setLayoutParams( params ); }
>
> void setWeight( final android.view.View view, final float weight )
> { final android.widget.LinearLayout.LayoutParams params
> = getParams( view );
> params.weight = 0; view.setLayoutParams( params ); }
Here you run into the second restriction. Java generics are based on
type erasure. This will only work for objects. Build in types cannot be
used as generic types because different machine code is required in general.
> In JavaScript, it would be easy as one could just pass in
> the name of the field as a parameter.
Of course, it is an interpreter language without a strong type system,
and that is also the reason why it is so slow and error prone. A
maintenance disaster from my point of view.
> In C or C++, one could pass the offset of the field.
No, in C++ a member pointer is the exact solution. Structure offsets are
only allowed for C style PODs. In C++ you would write something like
template <typename T>
void set(android.view.View& view, T LayoutParams::*field, const T& value)
{ auto params = getParams( view );
params.*field = value;
view.setLayoutParams( params );
}
set(my_view, &LayoutParams::width, 7);
This solution produces almost no runtime overhead. At most one addition
for the calculation of the field address and another instruction for
passing the additional parameter. But most likely the compiler will
inline the simple code fragment and generate exactly the same code than
with an explicit function.
> This is for Android, where there are no lambdas, otherwise
> one could pass in a lambda.
Java always had something like lambdas:
interface LayoutIntField
{ public void set(LayoutParams params, int value);
}
void set( final android.view.View view,
final LayoutIntField setter,
final int value )
{ final android.widget.LinearLayout.LayoutParams params
= getParams( view );
setter.set(params, value);
view.setLayoutParams( params );
}
set( my_view,
new LayoutIntField
{ public void set(LayoutParams params, int value)
{ params.width = value; }
}, 7 );
Not that pretty, but exactly the same would happen if you use lambdas.
You just save a few characters of code.
But it is straight forward to modify the interface to use closures:
interface LayoutModifier
{ public void modify(LayoutParams params);
}
void modifyLayout( final android.view.View view,
final LayoutModifier action )
{ final android.widget.LinearLayout.LayoutParams params
= getParams( view );
action.modify( params );
view.setLayoutParams( params );
}
modifyLayout( my_view,
new LayoutModifier
{ public void modify(LayoutParams params)
{ params.width = 7; }
} );
This is simpler and even more powerful, because you can do any action
not just setting a single integer value.
Again it is still the same result than with lambdas.
> But this would increase the size
> and complexity of each call site. Using anonymous classes
> (when there are no lambdas) would increase the call size
> even more.
You will always have some overhead since you need to pass an additional
parameter. But with a few tricks it could be reasonable.
E.g. if the runtime overhead does not count you might place the
anonymous classes as named instances in you class:
interface LayoutIntField
{ public void set(LayoutParams params, int value);
public int get(LayoutParams params); // might be useful too
}
static final LayoutIntField LayoutWidth =
new LayoutIntField
{ public void set(LayoutParams params, int value)
{ params.width = value; }
public int get(LayoutParams params, int value)
{ return params.width; }
};
set( my_view, LayoutWidth, 7 );
Now you have implemented a field reference, semantically similar to the
C++ memeber pointers, but still with runtime overhead because its
behavior is similar to virtual C# properties. (C# unfortunately is also
missing this kind of reference type. What a pity.)
> I could ressort to reflection, but this would also increase
> the size and complexity of the code.
... and is incredibly slow and error prone.
> So, I see no reasonable way to refactor this.
You have another option: use an enumeration combined with switch.
enum LayoutIntField
{ HEIGHT,
WIDTH,
...
};
void setInt( final android.view.View view,
final LayoutIntField field,
final int value )
{ final android.widget.LinearLayout.LayoutParams params
= getParams( view );
switch (field)
{ case LayoutIntField.HEIGHT: params.height = value; break;
case LayoutIntField.WIDTH: params.width = value; break;
...
}
setter.set(params, value);
view.setLayoutParams( params );
}
setInt( my_view, LayoutIntField.WIDTH, 7 );
Not that fancy but with only slight runtime and code overhead.
I have seen this idiom in several Java APIs. Sometimes even simpler and
not type safe by just using int and a bunch of static final int
constants instead of enum.
> It's a little
> bit sad, because DRY is what programming is all about.
Well, most languages have restrictions, even C++ templates although they
are rather tough. And much code can be expressed statically as part of a
sufficiently powerful type system. But this also increases the
complexity of the code, even though compilers can prevent significantly
more bugs in this case.
Marcel
Back to comp.lang.java.programmer | Previous | Next | Find similar | Unroll thread
Re: Refactoring three very similar methods into one Marcel Mueller <news.5.maazl@spamgourmet.org> - 2015-12-25 03:59 +0100
csiph-web