3
\$\begingroup\$

I've created a singleton by means of an interface. I don't want to make a thing with getInstance() etc because I don't care about inheritance, and it's redundant and non-declarative.

Are there downsides of doing it this way? Any redundancy I can remove? Is there a cleaner/more consise way to do this? I like doing it this way because methods are first class because I can pass them to map etc (e.g: map(Dog.sleep, someListOfNumbers)) and it supports currying if you have functions like F<Pair<X,Pair<Y,Z>>>.

Note the F interface is declared elsewhere, I just inlined it here to have less files.

package a;
import static a.Dog.State.*;
public interface Dog {
    interface F<X,Y> {
        Y f(X x);
    }

    static final class State {
        protected static boolean dead;
        protected static String name = "leo";
        protected static Integer age = 0;
    }

    F<Void,String> getName = new F<Void,String>() {
        public String f(Void _) {
            return name;
        }
    };

    F<Void,Void> die = new F<Void,Void>() {
        public Void f(Void _) {
            System.out.println("X_X");
            dead = true;
            return null;
        }
    };

    F<Void,Void> woof = new F<Void,Void>() {
        public Void f(Void _) {
            if (dead)
                throw new IllegalStateException("cannot woof when dead");
                System.out.println("woof");
            return null;
        }
    };

    F<Integer,Void> sleep = new F<Integer,Void>() {
        public Void f(Integer years) {
            if (dead)
                throw new IllegalStateException("cannot sleep when dead");
            age += years;
            return null;
        }
    };

    F<Void,Integer> getAge = new F<Void,Integer>() {
        public Integer f(Void _) {
            return age;
        }
    };
}

A test:

import a.Dog;
class Main {
    public static void main(String[] args) {
      System.out.println(
          "The dog's name is " + Dog.getName.f(null) +
          " and is " + Dog.getAge.f(null) + " years old"
      );
      Dog.sleep.f(2);
      System.out.println(
          "The dog's name is " + Dog.getName.f(null) +
          " and is " + Dog.getAge.f(null) + " years old"
      );
      Dog.woof.f(null);
      Dog.die.f(null);
      Dog.woof.f(null);
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I would think passing null in for no parameters would get a little tiresome, and also make the code a little less clear. \$\endgroup\$ – Jeff Vanzella May 16 '13 at 21:19
  • \$\begingroup\$ Where exactly in your code are you trying to create a singleton? \$\endgroup\$ – IgorGanapolsky Feb 19 '15 at 19:13
5
\$\begingroup\$

Your implementation fails to adhere to some important Object Oriented principles.

  1. Encapsulation

    The state of your singleton can be modified by others.

    package a;
    
    public class Cat implements Dog {
    
        static {
            State.age = 17;
        }
    
        public int age() {
            return Cat.getAge.f(null);
        }
    
        public void setAge(int age) {
            State.age = age;
        }
    
        public static void main(String[] args) {
            System.out.println(new Cat().age());
        }
    }
    
  2. Polymorphism

    This is basically a problem that comes with the singleton pattern itself. However, a more conventional approach to implementing a singleton can deal with this issue :

    package b;
    
    public interface Dog {
    
        String getName();
    
        void die();
    
        void woof();
    }
    

    and the singleton implementation :

    package b;
    
    public enum SingleDog implements Dog {
        INSTANCE;
    
        private String name;
        private boolean dead;
    
    
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public void die() {
            System.out.println("X_X");
            dead = true;
        }
    
        @Override
        public void woof() {
            if (dead) {
                throw new IllegalStateException("cannot woof when dead");
            }
            System.out.println("woof");
        }
    }
    

    This way clients can depend on the interface and not the singleton implementation. i.e. that there can only be one instance is now an implementation detail, and not an unchangeable design decision.

If you want to do functional programming, Java is probably not the language you should be using.

\$\endgroup\$
2
\$\begingroup\$

1) I don't see what this has to do with singletons. Surely you want to have the possibility of having more than one dog.

2) You clearly come from a functional programming background. When I first read your code, I thought it had mistakenly been labeled as Java. Java is a very rigid language, which makes it very good for team software development since everybody will write similar code. However when you start mixing in a completely different programming paradigm, it becomes very difficult for the team to be efficient. Scala is one language that mixes OO and functional programming and it is not being used that much in real application development because the code ends up a mishmash of different programming paradigms that is very difficult to follow. Google Guava does have a package for functional programming which is similar to what you did. Here is what they say:

Excessive use of Guava's functional programming idioms can lead to verbose, confusing, unreadable, and inefficient code. These are by far the most easily (and most commonly) abused parts of Guava, and when you go to preposterous lengths to make your code "a one-liner," the Guava team weeps.

So if you really want to stick to functional programming (which I am not against), you should probably not use Java.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.