Apexの外部クラスと内部クラス

Apexでは、最上位クラス（外部クラス）とクラスの内部で宣言できる内部クラスがあります。
内部クラスは最上位クラスの直下でのみ宣言できます。内部クラスの中には宣言できません。
Javaとのちがいは[こちら](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_java_diffs.htm)。

```apex
/** 外部クラス */
public class OuterClass {
   /** 内部クラス */
   public class InnerClass {
   }
}
```

内部クラスをpublic、globalで宣言するとほかのクラスで使用することができます。ほかのクラスから使用するときは型に`外部クラス.内部クラス`を使用します。

```apex
OuterClass.InnerClass myInnerClass = new OuterClass.InnerClass();
```

## 外部クラス
clsファイルの中で最上位で宣言できるものに下記があります。

- クラス（virtualクラス、abstructクラスを含む）
- テストクラス
- インターフェース
- 列挙型

### 補足

- アクセス修飾子はpublicまたはglobalである必要があります。省略することはできません。ただしテストクラスはアクセス修飾子が考慮されないのでprivateで定義できます。
- globalで宣言した内部クラスがある場合、外部クラスもglobalで宣言する必要があります。
- インターフェース、列挙型の内部にクラスを宣言できません。

## 内部クラス
内部クラスは最上位のクラス、テストクラスの内部に宣言されたクラスです。
下記は外部クラスの内部で宣言することができます。

- クラス（virtualクラス、abstructクラスを含む）
- インターフェース
- 列挙型

### 補足
- インターフェース、列挙型の内部に宣言することはできません。
- テストクラスは内部クラスとして宣言できません（API v28.0以降）。
- アクセス修飾子を付けなかった場合、privateの扱いになります。
- globalとpublicはほかのクラスからも参照できます。
- <font color="Red">内部クラスはstatic変数、staticメソッドを宣言することができません。</font>

## アクセス
<font color="Red">同じファイルで宣言されたクラスはprivateであっても参照できます。</font>

```apex
public with sharing class OuterClass {
    // public/privateの内部クラスにアクセス可能
    PublicInnerClass myPublicClass = new PublicInnerClass();
    PrivateInnerClass myPrivateClass = new PrivateInnerClass();

    public class InnerClass {
        // ほかのpublic/privateの内部クラスにアクセス可能
        PublicInnerClass myPublicClass = new PublicInnerClass();
        PrivateInnerClass myPrivateClass = new PrivateInnerClass();
    }

    public class PublicInnerClass {}
    private class PrivateInnerClass {}
}
```

フィールド、メソッドも同様、同じファイル内であればprivateであっても参照できます。

```apex
public with sharing class OuterClass {
    void doSomething() {
        // 内部クラスのprivateメンバにアクセス可能
        PrivateInnerClass myInnerClass = new PrivateInnerClass();
        String pu = myInnerClass.publicVar;
        String pr = myInnerClass.privateVar;
        myInnerClass.doPublicMethod();
        myInnerClass.doPrivateMethod();
    }

    class InnerClass {
        void doSomething() {
            // ほかの内部クラスのprivateメンバにアクセス可能
            PrivateInnerClass otherInnerClass = new PrivateInnerClass();
            String pu = otherInnerClass.publicVar;
            String pr = otherInnerClass.privateVar;
            otherInnerClass.doPublicMethod();
            otherInnerClass.doPrivateMethod();
        }
    }

    class PrivateInnerClass {
        public String publicVar = 'public';
        private String privateVar = 'private';

        public void doPublicMethod() {}
        private void doPrivateMethod() {}
    }
}
```

## 外部クラス/内部クラスとインターフェース
- 同じファイル内のほかの内部クラスで実装することができます。
- publicで宣言されたインターフェースは、ほかのクラスでも実装することができます。
- ほかのクラスで使用するときに、型が`外部クラス.インターフェース名`と長くなってしまいます。

    ```apex:【利用】インターフェースを含むファクトリクラス（悪い例）
    // 型がFeeではなく、FeeFactory.Feeとなり、冗長。
    FeeFactory.Fee myFee = FeeFactory.createAdultFee();
    ```

    ```apex:【実装】インターフェースを含むファクトリクラス（悪い例）
    public with sharing class FeeFactory {
        public static Fee createAdultFee() {
            return new AdultFee();
        }

        public interface Fee {
            String label();
            Double value();
        }

        // 内部クラスでもインターフェースを実装できる
        private class AdultFee implements Fee {
            public String label() {
                return 'おとな料金';
            }

            public Double value() {
                return 1000;
            }
        }
    }
    ```
- 外部クラスでも内部で宣言されたインターフェースを実装することができてしまいます。構造がわかりにくくなるので使用するするべきでないと思います。
    ```apex:【利用】内部のインターフェースを実装したクラス（悪い例）
    // 型名が「実装クラス.インターフェース」となり、構造がわかりにくい
    ChildFee.Fee myFee = new ChildFee();
    ```

    ```apex:【実装】内部のインターフェースを実装したクラス（悪い例）
    public with sharing class ChildFee implements Fee {
        public Double value() {
            return 400;
        }

        public String label() {
            return 'こども料金';
        }

        public interface Fee {
            String label();
            Double value();
        }
    }
    ```

### 例
publicのインターフェースは最上位で宣言するか、業務領域でインターフェースを一つのクラスにまとめてしまうのがわかりやすくてよいと思います。

```apex
Fee myFee = FeeFactory.createAdultFee();
```

```apex:インターフェース
public interface Fee {
    String label();
    Double value();
}
```

```apex:ファクトリクラス
public with sharing class FeeFactory {
    public static Fee createChildFee() {
        return new ChildFee();
    }

    public static Fee createAdultFee() {
        return new AdultFee();
    }

    private class ChildFee implements Fee {
        public String label() {
            return 'こども料金';
        }

        public Double value() {
            return 400;
        }
    }

    private class AdultFee implements Fee {
        public String label() {
            return 'おとな料金';
        }

        public Double value() {
            return 1000;
        }
    }
}
```

## 参考
- [Apexクラス定義 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_defining.htm)
- [拡張クラスの例 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_example.htm)
- [Apex クラスと Java クラスの違い | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_java_diffs.htm)