**Description**

Currently `BaseOperator` and `XComArgs` implements the same logic of `__lshift__`, `__rshift__` and others "chain" operations (`>>`, `<<`). It seems resonable to abstract this logic into `TaskMixin` to introduce some DRYness especially because the new concept of `TaskGroup` will also implement the same methods.

**Use case / motivation**

Limit duplication of logic.

**Related Issues**

https://github.com/apache/airflow/pull/10827#issuecomment-691927501
#10153 

airflow/models/baseoperator.py
self, parent: Any, template_fields: Iterable[str], context: Dict, jinja_env: jinja2.Environment,
        seen_oids: Set
    ) -> None:
        for attr_name in template_fields:
            content = getattr(parent, attr_name)
            if content:
                rendered_content = self.render_template(content, context, jinja_env, seen_oids)
                setattr(parent, attr_name, rendered_content)
    def render_template(      
        self, content: Any, context: Dict, jinja_env: Optional[jinja2.Environment] = None,
        seen_oids: Optional[Set] = None
    ) -> Any:
        """
        Render a templated string. The content can be a collection holding multiple templated strings and will
        be templated recursively.
        :param content: Content to template. Only strings can be templated (may be inside collection).
        :type content: Any
        :param context: Dict with values to apply on templated content
        :type context: dict
        :param jinja_env: Jinja environment. Can be provided to avoid re-creating Jinja environments during
            recursion.
        :type jinja_env: jinja2.Environment
        :param seen_oids: template fields already rendered (to avoid RecursionError on circular dependencies)
        :type seen_oids: set
        :return: Templated content
        """
        if not jinja_env:
            jinja_env = self.get_template_env()