We created a custom field type featuring a computed `classes` property that returns a simple array of generated CSS classes. The field itself allows content editors to define styling options depending on responsive breakpoints. Given the content editors choices, the CSS classes will be generated using a CSS class pattern defined within the field storage settings. For now, the property is generated and added to the field value within the `getValue()` method of our field item:

**src/Plugin/Field/FieldItem/ResponsiveClassItem.php**

    /**
     * Field item of type 'responsive_class'.
     *
     * @FieldType(
     *   id = "responsive_class",
     *   label = @Translation("Responsive class"),
     *   module = "responsive_class_field",
     *   default_widget = "responsive_class_widget",
     *   default_formatter = "responsive_class_formatter",
     *   cardinality = 1,
     *   translatable = FALSE,
     * )
     */
    class ResponsiveClassItem extends FieldItemBase implements FieldItemInterface {
      [...]

      /**
       * {@inheritdoc}
       */
      public static function schema(FieldStorageDefinitionInterface $field_definition) {
        return [
          'columns' => [
            'value' => [
              'description' => 'The selected responsive styles.',
              'type' => 'blob',
              'not null' => TRUE,
              'serialize' => TRUE,
            ],
          ],
        ];
      }

      /**
       * {@inheritdoc}
       */
      public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
        $properties = [];
        $properties['value'] = DataDefinition::create('responsive_class')
          ->setLabel(new TranslatableMarkup('Responsive class settings'))
          ->setRequired(FALSE);;

        $properties['classes'] = DataDefinition::create('responsive_class_classes')
          ->setLabel(new TranslatableMarkup('Generated responsive classes'))
          ->setComputed(TRUE)
          ->setReadOnly(TRUE);
    
        return $properties;
      }


      /**
       * {@inheritdoc}
       */
      public function getValue() {
        $values = parent::getValue();
        $values['classes'] = $this->getClasses($values['value']);
        return $values;
      }

      /**
       * Generate and return responsive classes.
       *
       * Uses the 'pattern' field storage setting to generate a class for every
       * breakpoint and return them as classes array. The pattern may contain
       * the placeholders {breakpoint} and {value} that will be replaced by the
       * configured breakpoint values.
       *
       * @param array $breakpoints
       *   Array of breakpoint values, whereas every breakpoint value is an
       *   associative array with the following keys:
       *   - breakpoint_id: ID of the breakpoint in the breakpoints definition.
       *   - breakpoint: Replacement value for the {breakpoint} token.
       *   - value: Replacement value for the {value} token.
       *
       * @return array
       *   Array of generated classes.
       */
      protected function getClasses(array $breakpoints) {
        $pattern = $this->getSetting('pattern');
        $classes = [];
        foreach ($breakpoints as $value) {
          $class = str_replace('{breakpoint}', $value['breakpoint'], $pattern);
          $class = str_replace('{value}', $value['value'], $class);
          $class = Html::getClass($class);
          $classes[] = $class;
        }
        return $classes;
      }
    
    }

**src/Plugin/DataType/ResponsiveClassClassesData.php**

    /**
     * Data type for responsive class field classes.
     *
     * @DataType(
     *   id = "responsive_class_classes",
     *   label = @Translation("Responsive Class Classes"),
     *   description = @Translation("Generated classes of the responsive class field."),
     * )
     */
    class ResponsiveClassClassesData extends TypedData {

    }

It lets us access the generated classes of the field via the value array and attach it to it's parent entities attribute classes like so:

**responsive_class_field.module**

    /**
     * Implements hook_entity_display_build_alter().
     */
    function responsive_class_field_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
      if (!$entity instanceof FieldableEntityInterface) {
        return;
      }

      $field_definitions = $entity->getFieldDefinitions();
      foreach ($field_definitions as $field_name => $field_definition) {
        if ($field_definition->getType() != 'responsive_class') {
          continue;
        }

        $field = $entity->get($field_name);
        if ($field->isEmpty()) {
          continue;
        }

        $classes = $field->getValue()[0]['classes'];
        foreach ($classes as $class) {
          $build['#attributes']['class'][] = $class;
        }
      }
    }

**And now coming to the real question**

While the above approach is working, I have the strong feeling that computed properties aren't intended to just being attached in the `getValue()` method.

Spending decent hours diving into core source code and field API documentation left me without a better solution. What is the 'Drupal way' to providing such a property?