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?

2
  • Is this feature in essence just a dynamic query?
    – Adrian Larson
    Mar 26, 2017 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.) Mar 26, 2017 at 19:16

2 Answers 2

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.

2
  • 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. :-) Mar 26, 2017 at 18:50
  • P.S. BTW, I'm a fan of verbose code, not just for being instructive, but also for being maintainable. ;-) Mar 26, 2017 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
    }
}
2
  • 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... Mar 26, 2017 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? Mar 26, 2017 at 10:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .