はじめに
ApexTypeImplementorはインターフェースを実装したクラスの情報を保持する標準オブジェクトです。
今回はこのApexTypeImplementorの使い方や利用シーンについて考察していきたいと思います。
※ApexTypeImplementorはSpring'22リリース時点でベータ機能となっています。今後機能が変更されたり、利用停止になる可能性もあるので、利用の際は注意してください。
ApexTypeImplementorについて
以下のようなインターフェースを定義します。
// カスタムインターフェース
public interface CustomInterface {
// 抽象メソッド
void execute();
}
このCustomInterfaceを実装するクラスを2つ定義します。
// インターフェースの実装クラスA
public class BusinessLogicA implements CustomInterface {
public void execute() {
System.debug('BusinessLogicA is called');
}
}
// インターフェースの実装クラスB
public class BusinessLogicB implements CustomInterface {
public void execute() {
System.debug('BusinessLogicB is called');
}
}
ここでApexTypeImplementorに対して以下のようなクエリを発行します。
SELECT InterfaceName, ClassName FROM ApexTypeImplementor WHERE InterfaceName = 'CustomInterface'
得られる結果は以下のようになります。
CustomInterfaceを実装した2つのクラス名が取得できました。
このようにApexTypeImplementorはインターフェースを実装したクラスの情報を保持しており、他のオブジェクトと同様にSOQLを使って検索することができます。
ApexTypeImplementorの応用
先程のインタフェースとクラスがある状態で以下のようなコードを実行してみます。
// CustomInterfaceを実装したクラス名のリストを取得
List<ApexTypeImplementor> implList = [SELECT ClassName FROM ApexTypeImplementor WHERE interfaceName = 'CustomInterface'];
// リストから先頭のApexTypeImplementorレコードを取得
ApexTypeImplementor impl = implList[0];
// 取得されたレコードのClassName項目からApexクラス名を取得し、動的にインスタンスを生成する。
CustomInterface logic = (CustomInterface) Type.forName(impl.ClassName).newInstance();
// 実装クラスの処理を呼び出す
logic.execute();
ログを見てみると以下のように出力されます。
BusinessLogicAの処理が呼ばれていることがわかります。
ここでのポイントはコードに「BusinessLogicA」の記述が一切ないにも関わらず、BusinessLogicAの処理を呼び出せていることです。ApexTypeImplementorで取得される実装クラス名の文字列をもとに、動的にインスタンス生成することでこのような処理を実現することができます。
ApexTypeImplementorで取得される結果は2件になります。今回はリストの1行目で取得されたBusinessLogicAが実行されましたが、以下のように2行目を指定するとBusinessLogicBが実行されます。
// リストの2行目のApexTypeImplementorレコードを取得(BusinessLogicBが選択される)
ApexTypeImplementor impl = implList[1];
どのApexTypeImplementorレコードを選択するかの条件を設定することで、条件に応じて実行クラスを呼び分けることもできます。
ApexTypeImplementor impl;
if (criteria) {
// BusinessLogicAを選択
impl = implList[0];
} else {
// BusinessLogicBを選択
impl = implList[1];
}
今回はたまたまBusinessLogicA、BusinessLogicBの順でレコードが取得されましたが、クエリの結果がこのような順にならない場合もあります。配列のインデックスで実装クラスを選択する場合は、ソートを行ってレコードの取得順を固定化する必要があることに注意してください。特定のインタフェースを実装しているクラスが1つしかないという前提であれば考慮は不要です。
活用シーン
-
Apex資産調査での活用
例えばApexでバッチ処理を実装する場合、Database.Batchableインターフェースを実装することが一般的です。組織のApexバッチを洗い出し一覧化したいときに、ApexTypeImplementorを使用することでよりスマートに対象資産を検索できます。以下のようなクエリを発行すれば簡単にApexバッチの一覧を取得できます。
SELECT ClassName FROM ApexTypeImplementor WHERE InterfaceNamespacePrefix = 'Database' AND InterfaceName = 'Batchable'
資産調査をする際にGrepでキーワード検索し一覧化することもあります。しかしGrep検索だと抜け漏れが発生したり、同一資産が重複検索されたりとオペレーションが煩雑になりがちです。ApexTypeImplementorを活用すれば、SOQL一発でインターフェースを実装したApexクラスをリスト化でき、煩雑なオペレーションからも解放されます。カスタムインターフェースを利用すれば、検索の幅も広がります。
-
マルチベンダー開発での活用
これはApexに限った話ではありませんが、マルチベンダー開発が行われるような状況で抽象化が役に立つケースがあります。例えばA社がクラスXの開発を担当し、B社がクラスYの開発を担当するようなケースです。
クラスXの中でクラスYを呼び出す必要があった場合、クラスYが完成しA社に提供されないとクラスXの開発ができません。
public class ClassX {
public void methodX() {
ClassY y = new ClassY(); // クラスYがないとコンパイルエラー発生
y.methodY();
}
}
public class ClassY {
public void methodY() {
System.debug('ClassY.methodY()');
}
}
コンパイルエラーを回避するためにダミーのクラスを作って開発もできますが、後々クラスを置き換える必要があったりと煩雑になってしまいます。2~3本であれば手作業でも問題ないかもしれませんが、数も多くなると手間も増えますしミスも起こりやすくなります。
このようなときにカスタムインターフェースで呼び出すメソッドの名称とパラメータと返り値さえ決めて抽象化しておけば、効率よく開発ができる場合があります。前述した通りApexTypeImplementorを使えば、呼び出すクラス名をコードに直接記述しなくても、抽象化したインターフェースを介して処理を呼ぶことができます。わざわざダミークラスを作らなくてもコンパイルエラーが起きません。モジュール同士を疎結合にすることで、仕様変更(例えば呼び出すクラス名が変わるなど)に対しても柔軟に対応できます。
テスト後のコードに手を入れなくてもよく、品質的な面でもメリットがあります。
public class ClassX {
public void methodX() {
List<ApexTypeImplementor> implList = [SELECT ClassName FROM ApexTypeImplementor WHERE InterfaceName = 'CustomInterface'];
ApexTypeImplementor impl = implList[0];
CustomInterface logic = (CustomInterface) Type.forName(impl.ClassName).newInstance();
logic.methodY(); // クラスYがなくてもコンパイルエラーは発生しない
}
}
public interface CustomInterface {
void methodY();
}
public class ClassY implements CustomInterface {
public void methodY() {
System.debug('ClassY.methodY()');
}
}
まとめ
今回はApexTypeImplementorを使ってインターフェース実装クラスの検索の方法と活用シーンについて考察してみました。
この仕組みを応用すればApex上でDependency Injection(依存性の注入)を実現するのにも役立ちそうです。
(機会があればDIについても記事にしたいと思います)
なかなか利用シーンは限られるかもしれませんが、インターフェースを使ったApex開発を行っている方は活用してみてください。