Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to share self-returning method-chain with ANY class?

UPDATE: I got it! It turns out that the "GENERICS" comment from Boris the Spider was exactly the clue I needed.

Original question and broken code first, solution below that. My generic design for needer-needable below that.

Original question and non-working code

On and off for the past few years, I have been trying to make self-returning method-chains "sharable" with other classes. This is to avoid the big pain of having to duplicate every self-returning function in every sub-class. The basic idea is that you pass the "needer" class to the "needable" class to start configuration, and then pass the needer back when configuration is over. The "needed" object is set internally.

For example:

ConfigUser cu = (new ConfigUser()).
   cfgFavNum().twentySeven().increment().timesTwo().endCfg().
   firstName("Kermit").lastName("Frog");

where

firstName("Kermit").lastName("Frog")

are part of the ConfigUser class, and

twentySeven().increment().timesTwo().endCfg()

comes from a separate "config the number" class. endCfg() returns the "needer" class (ConfigUser), at which point you should be able to continue the chain back in ConfigUser. But you can't. In the below code...and in every attempt I have made, I end up with the same error:

        C:\java\ConfigUser.java:4: cannot find symbol
        symbol  : method firstName(java.lang.String)
        location: interface NeedsFavNum
                 cfgFavNum().twentySeven().increment().timesTwo().endCfg().
                                                                          ^

If you comment out everything after endCfg() it reveals the problem:

ConfigUser cu = (new ConfigUser()).
   cfgFavNum().twentySeven().increment().timesTwo().endCfg();//.
   //firstName("Kermit").lastName("Frog");

         C:\java\ConfigUser.java:15: incompatible types
         found   : NeedsFavNum
         required: ConfigUser
                        cfgFavNum().twentySeven().increment().timesTwo().endCfg();//.
                                                                               ^

It can't return ConfigUser, which is sub-class of NeedsFavNum, which is the interface for all classes "needing" the favorite-number-config.

Of course, you can duplicate all the functions so they ALL return ConfigUser-s, but that defeats the purpose of sharing chains. The purpose is to share these chains among ANY class, not just sub-classes.

Is there any way to achieve this, or to rethink the whole issue? I'm starting to think that it is simply not possible.

The details are in the below code. It works (...up to these compilation errors, anyway): copy it into a file named ConfigUser.java and give it a try.

Thank you for helping me.

public class ConfigUser implements NeedsFavNum  {
   public static final void main(String[] igno_red)  {
      ConfigUser cu = (new ConfigUser()).
         cfgFavNum().twentySeven().increment().timesTwo().endCfg().
         firstName("Kermit").lastName("Frog");

      cu = (new ConfigUser()).                  cfgFavNum().twentySeven().increment().timesTwo().endCfg();//.
      //         firstName("Kermit").lastName("Frog");
   }
//init
   public static final int iDEFAULT_FAV = 8;
   int iFav = -1;
   String sName1st = null;
   String sNameLast = null;
//funcs
   public ConfigUser()  {
   }
   public ConfigUser firstName(String s_s)  {
      sName1st = s_s;
   }
   public ConfigUser lastName(String s_s)  {
      sNameLast = s_s;
   }
   public FavNumConfigurator cfgFavNum()  {
      return  new FavNumConfigurator(this, iDEFAULT_FAV);
   }
   public ConfigUser setNumReturnNeeder(int i_favFullyConfigured)  {
      iFav = i_favFullyConfigured;
      return  this;
   }
}
interface NeedsFavNum  {
   ConfigUser setNumReturnNeeder(int i_fav);
}
class FavNumConfigurator  {
   NeedsFavNum nfn = null;
   int iFav = -1;
   public FavNumConfigurator(NeedsFavNum nf_n, int i_defaultFav)  {
      nfn = nf_n;
      iFav = i_defaultFav;
   }
   public FavNumConfigurator twentySeven()  {
      iFav = 27;
   }
   public FavNumConfigurator timesTwo()  {
      iFav = iFav * 2;
   }
   public FavNumConfigurator increment()  {
      iFav += 1;
   }
   public NeedsFavNum endCfg()  {
      return  nfn.setNumReturnNeeder(iFav);
   }
}

Solution with working code

It turns out that the "GENERICS" comment from Boris the Spider was exactly the clue I needed. Instead of the "needable" class being

FavNumConfigurator

now its

FavNumConfigurator<R extends FavNumNeeder>

where FavNumNeeder is the "needer" interface for any class needing the favorite-number configuration chain. Now the endCfg() function can return exactly the class I want.

Here's the fixed example (it works--copy and save it as ConfigUser.java):

/**
   <P>The main class: the &quot;needer&quot;.</P>
 **/
public class ConfigUser implements NeedsFavNum  {
   public static final void main(String[] igno_red)  {
      ConfigUser cu = (new ConfigUser()).
         cfgFavNum().twentySeven().increment().timesTwo().timesTwo().endCfg().
         firstName("Kermit").lastName("Frog");

      System.out.println("name:         " + cu.sName1st + " " + cu.sNameLast);
      System.out.println("favorite-num: " + cu.iFav);

      //---OUTPUT:
      //name:         Kermit Frog
      //favorite-num: 112
   }
//init
   public static final int iDEFAULT_FAV = 8;
   int iFav = -1;
   String sName1st = null;
   String sNameLast = null;
//funcs
   public ConfigUser()  {
   }
   //Self-returning configurers...START
      public ConfigUser firstName(String s_s)  {
         sName1st = s_s;
         return  this;
      }
      public ConfigUser lastName(String s_s)  {
         sNameLast = s_s;
         return  this;
      }
   //Self-returning configurers...END
   //Start fav-num configuration. Returns the "needable"
   public FavNumConfigurator<ConfigUser> cfgFavNum()  {
      return  (new FavNumConfigurator<ConfigUser>(this, iDEFAULT_FAV));
   }
   //Called by the "needable" in endCfg()
   public ConfigUser setNumReturnNeeder(int i_favFullyConfigured)  {
      iFav = i_favFullyConfigured;
      return  this;
   }
}

//The "needer" interface, for all classes needing favorite-number
//configuration
interface NeedsFavNum  {
   ConfigUser setNumReturnNeeder(int i_fav);
}

//The "needable" class: A shareable function-chain for favorite-number
class FavNumConfigurator<R extends NeedsFavNum>  {
   R nfn = null;
   int iFav = -1;
   public FavNumConfigurator(R nf_n, int i_defaultFav)  {
      nfn = nf_n;
      iFav = i_defaultFav;
   }
   //Self-returning configurers...START
      public FavNumConfigurator<R> twentySeven()  {
         iFav = 27;
         return  this;
      }
      public FavNumConfigurator<R> timesTwo()  {
         iFav = iFav * 2;
         return  this;
      }
      public FavNumConfigurator<R> increment()  {
         iFav += 1;
         return  this;
      }
   //Self-returning configurers...END
   public R endCfg()  {
      nfn.setNumReturnNeeder(iFav);
      return  nfn;
   }
}

Generic needer-needable design

Here is my design of a generic needer-needable solution that implements the above fix. The hardest part was avoiding circular dependencies between ConfigNeedable and ConfigNeeder.

public interface Chainable  {
   Chainable chainID(Object o_id);
   Object getChainID();
}
public interface ConfigNeedable<O,R extends ConfigNeeder> extends Chainable  {
   boolean isAvailableToNeeder();
   ConfigNeedable<O,R> startConfigReturnNeedable(R c_n);
   R getActiveNeeder();
   boolean isNeededUsable();
   R endCfg();
}
public interface ConfigNeeder  {
   void startConfig();
   boolean isConfigActive();
   <O> Class<O> getNeededType();
   <O> void setNeeded(O o_fullyConfigured);
}

Here is the same (working) example that uses this design, but since it depends on implementations in my personal library (which is unreleased at the moment, because it's changing minute to minute as I'm working on it), it won't compile. Hopefully it will help someone to see.

import  xbn.lang.chain.ChainableComposer;
import  xbn.lang.chain.ConfigNeeder;
import  xbn.lang.chain.SimpleConfigNeedable;
import  xbn.lang.chain.SimpleConfigNeeder;

public class ConfigNeedableNeederXmpl  {
   public static final void main(String[] igno_red)  {
      UserSettings us = (new UserSettings()).
         cfgFavInt().twentySeven().timesTwo().increment().endCfg().name("President Obama");
      System.out.println("name=" + us.sName);
      System.out.println("favorite number=" + us.iFav);
   }
}
class UserSettings implements ConfigNeeder  {
   private SimpleConfigNeeder scn = new SimpleConfigNeeder(Integer.class);
   public static final int iDEFAULT_FAV = 8;
   public int iFav = -1;
   public String sName = null;

   public UserSettings name(String s_name)  {
      sName = s_name;
      return  this;
   }
   public FavNumConfigurator cfgFavInt()  {
      FavNumConfigurator fnc = new FavNumConfigurator();
      fnc.startConfigReturnNeedable(this);
      return  fnc;
   }
   //ConfigNeeder: composition implementation...START
      public <O> void setNeeded(O i_fullyConfigured)  {
         scn.setNeeded(i_fullyConfigured);
         iFav = (Integer)scn.getElimNeeded();
      }
      public void startConfig()  {
         scn.startConfig();
      }
      public boolean isConfigActive()  {
         return  scn.isConfigActive();
      }
      public <O> Class<O> getNeededType()  {
         return  scn.getNeededType();
      }
      public void endConfig()  {
         iFav = (Integer)scn.getElimNeeded();
      }
   //ConfigNeeder: composition implementation...END
}
class FavNumConfigurator extends SimpleConfigNeedable<Integer,UserSettings>  {
   public FavNumConfigurator()  {
      super(33, true);
   }
   public FavNumConfigurator(Integer o_defaultNeeded, boolean b_defaultNeededUsable)  {
      super(o_defaultNeeded, b_defaultNeededUsable);
   }
   public FavNumConfigurator set(int i_i)  {
      try  {
         updateObject(i_i);
      }  catch(RuntimeException rtx)  {
         throw  newRTXWChainID("set", rtx);
      }
      return  this;
   }
   public FavNumConfigurator twentySeven()  {
      updateObject(27);
      return  this;
   }
   public FavNumConfigurator timesTwo()  {
      updateObject(getNeededInProcess() * 2);
      return  this;
   }
   public FavNumConfigurator increment()  {
      updateObject(getNeededInProcess() + 1);
      return  this;
   }
}
like image 904
aliteralmind Avatar asked Nov 10 '22 15:11

aliteralmind


1 Answers

What you're looking for is effectively the C++ Curiously recurring template pattern.

You can put all your "shared" self-returning bits in a base abstract class, then extend it.

For example:

public abstract class Base<T extends Base<T>>
{
   protected abstract T self(); 

   protected String name;
   protected String address;

   public T withtName(String name)
   {
       this.name = name;
       return self();
    }

   public T withAddress(String address)
   {
       this.address = address;
       return self();
   }

}

class MyClass extends Base<MyClass>
{
    private String someOtherThing;

    public MyClass withSomeOtherThing(String thing)
    {
        this.someOtherThing = thing;
        return self();
    }

    @Override
    protected MyClass self()
    {
        return this;
    }    
 }

Now you can do:

MyClass mc = 
    new MyClass()
        .withAddress("111 elm")
        .withtName("Bob")
        .withSomeOtherThing("foo");
like image 140
Brian Roach Avatar answered Nov 15 '22 06:11

Brian Roach