6

While working-on/playing with the Build a Restaurant-Locator Lightning Component Trailhead, I wanted to develop a more clever (possibly read over-engineered) way of selecting address from either the Contact or the Account.

I was hoping I could put the input Id into a wrapper and then use an overloaded method (instead of conditional logic) to return the correct address.

So, I have this (working) code to wrap the Id:

public class SObjectIdHelper {
    public SObjectIdHelper.SObjectId getIdFor(String sObjectType, Id recordId) {
        if (sObjectType == 'Account') {
            return new AccountId(recordId);
        } else if (sObjectType == 'Contact') {
            return new ContactId(recordId);
        }
        throw new SObjectIdHelperException('Unsupported SObjectType: ' + sObjectType);  
    }

    public virtual class SObjectId {
        public Id id {get; private set;}
        public SObjectId(Id id){
            this.id = id;
        }
    }

    public class AccountId extends SObjectId {
        public AccountId(Id id){
            super(id);
        }
    }

    public class ContactId extends SObjectId  {
        public ContactId(Id id){
            super(id);
        }
    }
}

And I have this address helper:

public class ITA_AddressHelper {
    ITA_AccountHelper accountHelper;
    ITA_ContactHelper contactHelper;

    public ITA_AddressHelper(ITA_AccountHelper accountHelper, ITA_ContactHelper contactHelper) {
        this.accountHelper = accountHelper;
        this.contactHelper = contactHelper;
    }

    public ITA_AddressHelper() { 
        this(new ITA_AccountHelper(), new ITA_ContactHelper());
    }

    public String getAddress  (SObjectIdHelper.AccountId recordId) {
        return this.accountHelper.selectLocation(recordId.id);
    }

    public String getAddress  (SObjectIdHelper.ContactId recordId) {
        return this.contactHelper.selectLocation(recordId.id);
    }

    public String getAddress (SObjectIdHelper.SObjectId recordId) {
        throw new ITA_AddressHelperException('Unsupported SObject Id Type');
    }
}

My InTheArea class contains this method:

private String request(String searchTerm, String sObjectType, Id recordId) {
    try {
        SObjectIdHelper.SObjectId typedId = this.sObjectIdHelper.getIdFor(sObjectType, recordId);
        return this.yelpWrapper.request(searchTerm, this.addressHelper.getAddress(typedId));   
    }
    catch (Exception ex) {
        return '{' + 
                '"error" : "This component cannot be used on this page type: ' + ex.getMessage() + '"' 
            + '}';
    }
} 

Unfortunately, this will fail because SFDC doesn't seem to leverage that typedId is actually an instance of either SObjectIdHelper.AccountId or SObjectIdHelper.ContactId.

I can make this work like

public class ITA_AddressHelper {
    ClassHelper classHelper; 
    ITA_AccountHelper accountHelper;
    ITA_ContactHelper contactHelper;

    private Map<String, ProvidesLocation> locationProviderByIdTypeMap; 

    public ITA_AddressHelper(ClassHelper classHelper, ITA_AccountHelper accountHelper, ITA_ContactHelper contactHelper) {
        this.classHelper = classHelper;
        this.accountHelper = accountHelper;
        this.contactHelper = contactHelper;

        locationProviderByIdTypeMap = new Map<String, ProvidesLocation>{
            'AccountId' => accountHelper,
            'ContactId' => contactHelper
        };
    }

    public ITA_AddressHelper() { 
        this(new ClassHelper(), new ITA_AccountHelper(), new ITA_ContactHelper());
    }

    public String getAddress (SObjectIdHelper.SObjectId recordId) {
        String idType = classHelper.getNameFor(recordId);
        ProvidesLocation locationProvider = locationProviderByIdTypeMap.get(idType);
        if (locationProvider != null){
            return locationProvider.selectLocation(recordId.id);
        } else {
            throw new ITA_AddressHelperException('Unsupported SObject Id Type');    
        }
    } 
}

But now I need to mess around with maps and getting the wrapper class name.

Is there a better way to make this work?

  • Is this feature in essence just a dynamic query? – Adrian Larson Mar 26 '17 at 14:01
  • @AdrianLarson, dynamic SOQL could be another approach, but in this context, I prefer this one. (Also, I'm not presently trying to solve a real problem, just fit Enterprise-ready patterns to SFDC tutorial materials.) – Brian Kessler Mar 26 '17 at 19:16
7

Yes, I would say there is a much better way. Use the Id.getSObjectType method.

public String getAddress(Id recordId)
{
    if (recordId == null) return null;

    SObjectType idType = recordId.getSObjectType();
    // further logic
}

Now, if you can set up your helper classes to just use an empty constructor, you could dynamically instantiate them based on this information. Just have them implement a common interface.

Helpers

public interface AddressHelper
{
    String getAddress(Id recordId);
}
public class AccountHelper implements AddressHelper
{
    public String getAddress(Id recordId) { /*implementation*/ }
}
public class ContactHelper implements AddressHelper
{
    public String getAddress(Id recordId) { /*implementation*/ }
}

Dynamic Instantiation

static Map<SObjectType, Type> sObjectToHelper = new Map<SObjectType, Type>
{
    Account.sObjectType, AccountHelper.class,
    Contact.sObjectType, ContactHelper.class
};

public String getAddress(Id recordId)
{
    if (recordId == null) return null;

    SObjectType idType = recordId.getSObjectType();
    if (!sObjectTypeToHelper.containsKey(idType)) return null;

    Type helperType = sObjectTypeToHelper.get(idType);
    AddressHelper helper = (AddressHelper)helperType.newInstance();
    return helper.getAddress(recordId);
}

The above is a bit more verbose than strictly necessary, but I believe it will be more instructive as such.

  • Definitely an improvement! means I shouldn't even need the hasSObjectName on the Ltng component, nor as a parameter. Also reduces instantiation of helper classes I don't need. I was hoping to figure out something with method overloading, but in retrospect, the Map does seem like a better way to go. :-) – Brian Kessler Mar 26 '17 at 18:50
  • P.S. BTW, I'm a fan of verbose code, not just for being instructive, but also for being maintainable. ;-) – Brian Kessler Mar 26 '17 at 19:46
2

Hope this example will help you

With condition:

public class exec{
    public void execute(String procType){
        if(procType='PROC1'){
            //do this 
        }

        if(procType='PROC2'){
            //do that
        }
    }
}

With polymorphism:

public class exec{
    public void execute(IProcess proc){
        proc.execute();
    }
}

public interface IProcess{
    void execute();
}

public class Process1 implements IProcess{
    public void execute(){
        // do this
    }
}

public class Process2 implements IProcess{
    public void execute(){
        // do that
    }
}
  • Let me think about that... On its own, the Strategy pattern doesn't seem to help, but maybe if I think my abstractions and their relationships I can do something with it... – Brian Kessler Mar 26 '17 at 10:01
  • I think it helps a little, but it still doesn't get me exactly where I would prefer to go because I need to create a map, get the name of the id wrapper class, and then get the appropriate class from the map (see updated code above) ... is there a way we could drop the map out? – Brian Kessler Mar 26 '17 at 10:22

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.