开发者

Java: Is there a way to you enforce a implementation of private methods?

开发者 https://www.devze.com 2022-12-15 08:36 出处:网络
I have 5 or 6 classes that I want to have follow the same basic st开发者_如何转开发ructure internally. Really most of those that the classes should follow are just for the use of the function itself,

I have 5 or 6 classes that I want to have follow the same basic st开发者_如何转开发ructure internally. Really most of those that the classes should follow are just for the use of the function itself, so I really want these methods to be private.

Is there any way to achieve this? I know interfaces would work great but they won't take private members and won't allow you to redefine the scope in the implemented method. Is there any workaround for this?

Thanks


I think the closest you can get is using an abstract class with abstract protected methods:

abstract class A {
    protected abstract void foo();
}

class B extends A {
    protected void foo() {}
}

To define common logic, you can call the protected method from a private method in the super class:

abstract class A {
    private void bar() {
        // do common stuff
        foo();
    }
    protected abstract void foo();
}

This way, you can allow subclasses to fill the private common template method with specific behavior.


Create an abstract base class that outlines the structure and common flow. Specify abstract methods for the steps in the flow that must be implemented by the inheriting classes.


Hmm, private functions can't be called by any other classes, even by subclasses. So what's the point in having private functions with the same name in different classes?


There is no way to enforce it at compile time, but you can write a unit test or a simple program to test for the existence of the methods using reflection.

I assume you are doing this to make the classes consistent for aesthetics/design reasons. If you are doing it for some other reason you should really use the abstract protected way others are suggesting.

Here is some code to get you started on such a tool/unit tests (you should improve the error messages at the very least, and I would really suggest unit tests rather then what I have here):

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class Main
{
    public static void main(String[] args) 
    {
        check(B.class, Modifier.PRIVATE, void.class, "doit", new Class<?>[] { int.class });
        check(C.class, Modifier.PRIVATE, void.class, "doit", new Class<?>[] { int.class });
    }

    private static void check(final Class<?>   clazz,
                              final int        modifiers,
                              final Class<?>   returnType,
                              final String     name,
                              final Class<?>[] params)
    {
        try
        {
            final Method method;

            method = clazz.getDeclaredMethod(name, params);

            if(method.getModifiers() != modifiers)
            {
                System.out.println("modifiers do not match");
            }

            if(method.getReturnType() != returnType)
            {
                System.out.println("return type does not match");
            }
        }
        catch(final NoSuchMethodException ex)
        {
            System.out.println("could not find method");
        }
    }
}

interface A
{
    void foo();
}


class B
    implements A
{
    public void foo()
    {
        doit(0);
    }

    private void doit(final int x)
    {
    }
}

class C
    implements A
{
    public void foo()
    {
        doit(0);
    }

    private int doit(final int x)
    {
        return (5);
    }
}


Create an outline 'common' class, with all your private methods on them.

Then create your 5 or 6 classes , each which have a field on there of type 'common'. You won't be able to call the private methods of course (but you say these are really internal to the class) - you'll have to advertise some public methods to alter state as well of course.

public class common { 
    private method1() { ; }
    private method2() { ; }
    public other() { ; }
...
}

public class myclass1 { 
    common commonMethods;
}

public class myclass2 { 
    common commonMethods;
}

or even (assume 'common' is defined as above):

public class template {
    common commonMethods;
}

public class myclass1 extends template {
...
}

So you get a (package-protected) 'commonMethods' field for 'free' on each of 5 or 6 subclasses.

After subsequent discussion on this thread, it appears the author doesn't actually want to share logic : just method signatures essentially , so this answer doesn't fit with that requirement.


While the interface methods themselves must always be public, you could make the interface package private and keep all of your Car (for example) implementations in the same package.

package com.some.car.pkg;

interface Car
{
    public void gas();
    public void brake();
}

Even though the methods are public, it doesn't matter since outside of the package com.some.car.pkg, Car is not visible. This way, all of your implementers would not be forced to extend an abstract class. The fact that you want common methods means truly private isn't the real solution, and IMHO, you want an interface, since it sounds like in your case an abstract class isn't quite right as there is no shared logic.

My 2 cents.


The "throw MethodNotImplementedException();" might be a useful construct.


If abstract protected really isn't protected enough, I wonder what the concern is. In any case, an alternative similar to monojohnny's would be to use the strategy pattern. This ensures that:

  • derived classes must define the behavior
  • derived classes can't access the behavior after defining it
  • instances can't access one another's behavior

E.g., with apologies for borrowing the car metaphor despite no automotive chops:

public interface GearBoxStrategy {
    public void changeGear(int newGear);
}

abstract public class Car {
    private GearBoxStrategy gearBox;
    public Car(GearBoxStrategy g) {
       this.gearBox = g;
    }

    public void accelerate(double targetSpeed) {
        int gear = getTargetGear(targetSpeed):
        gearBox.shift(gear);
    }
}

public class AutomaticTransmissionCar {
    public AutomaticTransmissionCar() {
        super(new AutomaticTransmissionGearBoxStrategy());
    }
}

public class ManualTransmissionCar {
    public ManualTransmissionCar() {
        super(new ManualTransmissionGearBoxStrategy());
    }
}


Create an abstract base class with a method marked final that describes the common flow that includes your private methods. Marking it as final means that it can't be extended by subclasses and thus the business logic is enforced as long as your calling code utilizes it. Extension points can be created by marking methods as protected. For example say you have a class that represents a retail store.

private final void doTransaction() {
    float amountDue;

    // a protected or abstract method that extenders can override
    Collection items = this.unloadShoppingCart();

    for (Object item : items) {
        // another protected or abstract method
        amountDue +=  this.getPrice(item);
    }

    // your private method 
    amountDue += this.getSalesTax(amountDue);

}


Is it possible to make all the classes inherit from the same base class?

If so, one thing you could consider would be at runtime in the base class's constructor use reflection to validate that the subclass is following the rules you describe, and throw an exception if it fails your validation rules.

The naive implementation of this test of course would have significant performance issues, so you'd have to be pretty clever about the way you implement the test.

For a start, the test should only be run once for all instances of a particular subtype T. So, you would have to cache the validation information somewhere. One way to do this would be to use some kind of static (global) hash table in the base class keyed on the type of each subtype.

You would also have to perform some kind of thread safe synchronization around this cache. What you really need to avoid on this is a performance hit for reads. What I've done in a similar case before was use a combination of the double check locking pattern and the use of an immutable hashtable so that you only take a performance hit for locking when attempting to write to the hashtable (i.e. when you create the first instance of a particular subtype T).

I'm actually not experienced in Java, what I describe, I implemented in .NET, which is why I can't provide you with a code example, but all the concepts should be easily transferable to Java - everything I mention is (AFAIK) available on both platforms.


Take a look at XDepend, it uses reflection to create a database based on your compiled code.

http://www.xdepend.com

It's aimed at software architects who wish to be able to quickly check potentially large libraries of compiled code for potential problem areas. It has inbuilt reports and visualization for such things as relationships between classes, cyclomatic complexity, coupling etc. etc.

In addition, it includes an inbuilt sql like query language "CQL" (for "code query language"). Using CQL you can define your own reports. You probably should be able to use it to define a report for violations of the rules you describe. Also, you can embed CQL queries directly into your code using annotations.

I haven't looked into it, but have used it's .NET equivalent 'NDepend', and it's a very cool tool.

Of course, you could also write your own custom tool which uses reflection to check your specific rules. XDepend may still be worth looking at though - it should be a lot more flexible.


Here's an idea: write a simple text parser to check for the existence of the methods. Include it as a task in Ant. As long as you are insisting on some form of coding standard, some simple text-matching should do it, ie, simply look for the formatted signature in the required source files.


In a comment you wrote "Yes that is the whole point. I know they can be called different things but I don't want them to be."

Now, some people might just say "that's impossible" but like most things in programming, it's not actually impossible, it's just a lot of work.

If you really want to do this, you can create a custom Java Annotation for your class and then write an Annotation processor and call apt as part of your build process.

Like I said a lot of work, but it might be worthwhile if you want to learn how Annotations work.


Writing annotations is actually pretty simple. They work kind of like regular classes. For example, if you just want to mark a class for some reason you can create an empty or marker annotation like this

public @interface Car { }

Then in your Annotation Processor you can check to make sure Car has the right private methods.

I've written my own annotations, but I checked them at Runtime using the reflection API, rather then at build time. They are actually pretty easy.

0

精彩评论

暂无评论...
验证码 换一张
取 消