10

Let's say I have 1 complete class with around 20 methods which provide different functionalities.

Now we have multiple clients using this class, but we want them to have restricted access.

For e.g. -

Client 1 - Gets access to method1/m3/m5/m7/m9/m11

Client 2 - Gets access to method2/m4/m6/m8/m10/m12

Is there any way I can restrict this access?

One solution which I thought:

Create 2 new classes extending Parent class and override methods which are not accessible and throw Exception from them. But then if 3rd client with different requirement, we have to create new subclass for them.

Is there any other way to do this?

Thinker
  • 6,820
  • 9
  • 27
  • 54
  • The answer here is ultimately opinion driven. Have you heard about Facade pattern? Do you insist on every client being able to see every method but not invoke it? What other stack do you have besides pure Java? – M. Prokhorov Sep 27 '19 at 12:27
  • @Nexevis - There can be overlap as well. Client 3 wants to access m1/m2/m3/m4/m5 – Thinker Sep 27 '19 at 12:28
  • 1
    What do you mean by `Client`? If it's just some other classes I think there's a problem with your design. You must break your class into smaller classes with more restricted functionalities! As long as there's a class that you don't want one (or more) of its functionalities in somewhere in your code, it's not broken enough. – Hamidreza Oct 07 '19 at 15:00

6 Answers6

16

Create 2 new classes extending Parent class and override methods which are not accessible and throw Exception from them. But then if 3rd client with different requirement, we have to create new subclass for them.

It is a bad solution because it violates Polymorphism and the Liskov Substitution Principle. This way will make your code less clear.

At first, you should think about your class, are you sure that it isn't overloaded by methods? Are you sure that all of those methods relate to one abstraction? Perhaps, there is a sense to separate methods to different abstractions and classes?

If there is a point in the existence of those methods in the class then you should use different interfaces to different clients. For example, you can make two interfaces for each client

interface InterfaceForClient1 {
  public void m1();
  public void m3();
  public void m5();
  public void m7();
  public void m9();
  public void m11();
}

interface InterfaceForClient2 {
  public void m2();
  public void m4();
  public void m6();
  public void m8();
  public void m10();
  public void m12();
}

And implement them in your class

class MyClass implements InterfaceForClient1, InterfaceForClient2 {
}

After it, clients must use those interfaces instead of the concrete implementation of the class to implement own logic.

fedup
  • 1,209
  • 11
  • 26
Maksym Fedorov
  • 6,383
  • 2
  • 11
  • 31
  • @Thinker if another client needs to use different methods then it should also use interface. But you should more think about how to create interfaces in this case, because of all depends on the concrete situation – Maksym Fedorov Oct 07 '19 at 06:29
15

You can create an Interface1 which defines methods only for Client1, and an Interface2 which defines methods only for Client2. Then, your class implements Interface1 and Interface2.

When you declare Client1 you can do something like: Interface1 client1. With this approach, client1 can accesses only methods of this interface.

I hope this will help you.

Kevin
  • 16,549
  • 8
  • 60
  • 74
Texx
  • 181
  • 1
  • 4
  • 1
    I think this is a good approach because even if you keep on adding clients,you are not disturbing any pre existing methods for other clients.Lets say if client3 comes into picture you create a new interface and implement it in the class.Also i think this kind of software design is one of the principles of SOLID. – CodeOfLife Oct 01 '19 at 14:55
3

The other answers already present the idiomatic approach. Another idea is a dynamic proxy decorating the API with an access check.

In essence, you generate a proxy API that has additional checks on method calls to implement a form of Access Control.

Example Implementation:

package com.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@FunctionalInterface
public interface ACL<P, Q> {

    boolean allowed(P accessor, Q target, Method method, Object[] args);

    class ACLException extends RuntimeException {
        ACLException(String message) {
            super(message);
        }
    }

    @SuppressWarnings("unchecked")
    default Q protect(P accessor, Q delegate, Class<Q> dType) {
        if (!dType.isInterface()) {
            throw new IllegalArgumentException("Delegate type must be an Interface type");
        }

        final InvocationHandler handler = (proxy, method, args) -> {
            if (allowed(accessor, delegate, method, args)) {
                try {
                    return method.invoke(delegate, args);
                } catch (InvocationTargetException e) {
                    throw e.getCause();
                }
            } else {
                throw new ACLException("Access denies as per ACL");
            }
        };

        return (Q) Proxy.newProxyInstance(dType.getClassLoader(), new Class[]{dType}, handler);
    }
}

Example Usage:

package com.example;

import java.lang.reflect.Method;

public class Main {

    interface API {
        void doAlpha(int arg);

        void doBeta(String arg);

        void doGamma(Object arg);
    }

    static class MyAPI implements API {
        @Override
        public void doAlpha(int arg) {
            System.out.println("Alpha");
        }

        @Override
        public void doBeta(String arg) {
            System.out.println("Beta");
        }

        @Override
        public void doGamma(Object arg) {
            System.out.println("Gamma");
        }
    }

    static class AlphaClient {
        void use(API api) {
            api.doAlpha(100);
            api.doBeta("100");
            api.doGamma(this);
        }
    }

    public static class MyACL implements ACL<AlphaClient, API> {
        @Override
        public boolean allowed(AlphaClient accessor, API target, Method method, Object[] args) {
            final String callerName = accessor.getClass().getName().toLowerCase();
            final String methodName = method.getName().toLowerCase().replace("do", "");
            return callerName.contains(methodName);
        }
    }


    public static void main(String[] args) {
        final MyACL acl = new MyACL();
        final API api = new MyAPI();
        final AlphaClient client = new AlphaClient();

        final API guardedAPI = acl.protect(client, api, API.class);
        client.use(guardedAPI);
    }
}

Notes:

  1. The accessor does not have to be the client object itself, it can be a string key or token that helps ACL identify the client.

  2. The ACL implementation here is rudimentary, more interesting ones could be One that reads ACL from some file or One that uses method and client annotations as rules.

  3. If you don't want to define an interface for API class, consider a tool like javassist to directly proxy a class.

  4. Consider other popular Aspect Oriented Programming solutions

S.D.
  • 29,290
  • 3
  • 79
  • 130
1

You should create one super class with all the methods and then provide Client specific implementations in their corresponding sub classes extending from the super class defined earlier.

If there are methods which are common implementation for all clients, leave their implementations to the super class.

Avinash Sagar
  • 527
  • 4
  • 10
1

It seems like you are a bit confused about the purpose of Classes and Interfaces. As far as I know, an Interface is a contract defining which functionality a piece of software provides. This is from official java tutorial:

There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts.

Then you can write a Class which implements this Interface/contract, that is, provides the code that actually perform what was specified. The List interface and the ArrayList class are both an example of this.

Interfaces and Classes have access modifiers, but they aren't designed to specify permissions to specific clients. They specify what is visible for other piece of software depending the location where it is defined: Class, Package, Subclass, World. For example, a private method can be accessed only inside the class where it is defined.

From official Java tutorial again:

Access level modifiers determine whether other classes can use a particular field or invoke a particular method. There are two levels of access control:

  • At the top level—public, or package-private (no explicit modifier).
  • At the member level—public, private, protected, or package-private (no explicit modifier).

Maybe you want something more powerful like Access Control List (ACL).

Diego Marin Santos
  • 1,923
  • 2
  • 15
  • 29
1

Your question is a little unclear, leading to different possible answers. I'll try to cover some of the possible areas:

Object encapsulation

If your goal is to provide interfaces to different clients that only provide certain functionality or a specific view there are several solutions. Which matches best depends on the purpose of your class:

Refactoring

The question somehow suggests that your class is responsible for different tasks. That might be an indicator, that you could tear it apart into distinct classes that provide the different interfaces.

Original

class AllInOne {
    A m1() {}
    B m2() {}
    C m3() {}
}
client1.useClass(allInOneInstance);
client2.useClass(allInOneInstance);
client3.useClass(allInOneInstance);

Derived

class One {
    A m1() {}
}

class Two {
    B m2() {}
}

class Three {
    C m3() {}
}
client1.useClass(oneInstance);
client2.useClass(twoInstance);
client3.useClass(threeInstance);

Interfaces

If you choose to keep the class together (there might be good reasons for it), you could have the class implement interfaces that model the view required by different clients. By passing instances of the appropriate interface to the clients they will not see the full class interface:

Example

class AllInOne implements I1, I2, I3 {
    ...
}
interface I1 {
    A m1();
}

But be aware that clients will still be able to cast to the full class like ((AllInOne) i1Instance).m2().

Inheritance

This was already outline in other answers. I'll therefore skip this here. I don't think this is a good solution as it might easily break in a lot of scenarios.

Delegation

If casting is a risk to you, you can create classes that only offer the desired interface and delegate to the actual implementation:

Example

class Delegate1 {
    private AllInOne allInOne;
    public A m1() {
        return allInOne.m1();
    }
}

Implementing this can be done in various ways and depends on your environment like explicit classes, dynamic proxies , code generation, ...

Framework

If you are using an Application Framework like Spring you might be able to use functionality from this Framework.

Aspects

AOP allows you to intercept method calls and therefor apply some access control logic there.

Security

Please note that all of the above solutions will not give you actual security. Using casts, reflection or other techniques will still allow clients to obtain access to the full functionality.

If you require stronger access limitations there are techniques that I will just briefly outline as they might depend on your environment and are more complex.

Class Loader

Using different class loaders you can make sure that parts of your code have no access to class definitions outsider their scope (used e.g. in tomcat to isolate different deployments).

SecurityManager

Java offers possibilities to implement your own SecurityManager this offers ways to add some extra level of access checking.

Custom build Security

Of course you can add your own access checking logic. Yet I don't think this will be a viable solution for in JVM method access.

Christian Frommeyer
  • 1,390
  • 1
  • 12
  • 20